mirror of
https://github.com/MontFerret/ferret.git
synced 2025-08-13 19:52:52 +02:00
Hello world
This commit is contained in:
126
.gitignore
vendored
Normal file
126
.gitignore
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
### Linux template
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
### Windows template
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
### Go template
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
### VisualStudioCode template
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
|
||||
# Dep
|
||||
|
||||
vendor
|
||||
bin
|
201
Gopkg.lock
generated
Normal file
201
Gopkg.lock
generated
Normal file
@@ -0,0 +1,201 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a62f6ed230a8cd138a9efbe718e7d0b0294f139266f5f55cd942769a9aac8de2"
|
||||
name = "github.com/PuerkitoBio/goquery"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "dc2ec5c7ca4d9aae063b79b9f581dd3ea6afd2b2"
|
||||
version = "v1.4.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:66b3310cf22cdc96c35ef84ede4f7b9b370971c4025f394c89a2638729653b11"
|
||||
name = "github.com/andybalholm/cascadia"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "901648c87902174f774fac311d7f176f8647bdaa"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:5e79f7a65816b2ba311439df21c9b24af91868cbb5d67489be6d94be2cccb286"
|
||||
name = "github.com/antlr/antlr4"
|
||||
packages = ["runtime/Go/antlr"]
|
||||
pruneopts = "UT"
|
||||
revision = "bdc05c87be2ad981744223df0fd745e8345baba9"
|
||||
version = "4.7.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b95738a1e6ace058b5b8544303c0871fc01d224ef0d672f778f696265d0f2917"
|
||||
name = "github.com/chzyer/readline"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "62c6fe6193755f722b8b8788aa7357be55a50ff1"
|
||||
version = "v1.4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:cc439e1d9d8cff3d575642f5401033b00f2b8d0cd9f859db45604701c990879a"
|
||||
name = "github.com/corpix/uarand"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "2b8494104d86337cdd41d0a49cbed8e4583c0ab4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:f14d1b50e0075fb00177f12a96dd7addf93d1e2883c25befd17285b779549795"
|
||||
name = "github.com/gopherjs/gopherjs"
|
||||
packages = ["js"]
|
||||
pruneopts = "UT"
|
||||
revision = "0210a2f0f73c96103378b0b935f39868e5731809"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7b5c6e2eeaa9ae5907c391a91c132abfd5c9e8a784a341b5625e750c67e6825d"
|
||||
name = "github.com/gorilla/websocket"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "66b9c49e59c6c48f0ffce28c2d8b8a5678502c6d"
|
||||
version = "v1.4.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1e15f4e455f94aeaedfcf9c75b3e1c449b5acba1551c58446b4b45be507c707b"
|
||||
name = "github.com/jtolds/gls"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "77f18212c9c7edc9bd6a33d383a7b545ce62f064"
|
||||
version = "v4.2.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:383ac09778833e583aa5f74cf52a71367c72ba43a7de1460e8ff95182ad93b3b"
|
||||
name = "github.com/mafredri/cdp"
|
||||
packages = [
|
||||
".",
|
||||
"devtool",
|
||||
"internal/errors",
|
||||
"protocol",
|
||||
"protocol/accessibility",
|
||||
"protocol/animation",
|
||||
"protocol/applicationcache",
|
||||
"protocol/audits",
|
||||
"protocol/browser",
|
||||
"protocol/cachestorage",
|
||||
"protocol/console",
|
||||
"protocol/css",
|
||||
"protocol/database",
|
||||
"protocol/debugger",
|
||||
"protocol/deviceorientation",
|
||||
"protocol/dom",
|
||||
"protocol/domdebugger",
|
||||
"protocol/domsnapshot",
|
||||
"protocol/domstorage",
|
||||
"protocol/emulation",
|
||||
"protocol/headlessexperimental",
|
||||
"protocol/heapprofiler",
|
||||
"protocol/indexeddb",
|
||||
"protocol/input",
|
||||
"protocol/inspector",
|
||||
"protocol/internal",
|
||||
"protocol/io",
|
||||
"protocol/layertree",
|
||||
"protocol/log",
|
||||
"protocol/memory",
|
||||
"protocol/network",
|
||||
"protocol/overlay",
|
||||
"protocol/page",
|
||||
"protocol/performance",
|
||||
"protocol/profiler",
|
||||
"protocol/runtime",
|
||||
"protocol/schema",
|
||||
"protocol/security",
|
||||
"protocol/serviceworker",
|
||||
"protocol/storage",
|
||||
"protocol/systeminfo",
|
||||
"protocol/target",
|
||||
"protocol/testing",
|
||||
"protocol/tethering",
|
||||
"protocol/tracing",
|
||||
"rpcc",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "75b0ecc5efcff27ac756a33ec71f0db75dc3d21c"
|
||||
version = "v0.19.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4ca145a665316d3c020a39c0741780fa3636b9152b824206796c4dce541f4a24"
|
||||
name = "github.com/sethgrid/pester"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "03e26c9abbbf5accb8349790bf9f41bde09d72c3"
|
||||
version = "1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cc1c574c9cb5e99b123888c12b828e2d19224ab6c2244bda34647f230bf33243"
|
||||
name = "github.com/smartystreets/assertions"
|
||||
packages = [
|
||||
".",
|
||||
"internal/go-render/render",
|
||||
"internal/oglematchers",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "7678a5452ebea5b7090a6b163f844c133f523da2"
|
||||
version = "1.8.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a3e081e593ee8e3b0a9af6a5dcac964c67a40c4f2034b5345b2ad78d05920728"
|
||||
name = "github.com/smartystreets/goconvey"
|
||||
packages = [
|
||||
"convey",
|
||||
"convey/gotest",
|
||||
"convey/reporting",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "9e8dc3f972df6c8fcc0375ef492c24d0bb204857"
|
||||
version = "1.6.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:24641a15105cc4281b8a4a701ac4019194618102c327b1495485b3f19ffdd53e"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
"html",
|
||||
"html/atom",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "26e67e76b6c3f6ce91f7c52def5af501b4e0f3a2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:39ebcc2b11457b703ae9ee2e8cca0f68df21969c6102cb3b705f76cca0ea0239"
|
||||
name = "golang.org/x/sync"
|
||||
packages = ["errgroup"]
|
||||
pruneopts = "UT"
|
||||
revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/PuerkitoBio/goquery",
|
||||
"github.com/antlr/antlr4/runtime/Go/antlr",
|
||||
"github.com/chzyer/readline",
|
||||
"github.com/corpix/uarand",
|
||||
"github.com/mafredri/cdp",
|
||||
"github.com/mafredri/cdp/devtool",
|
||||
"github.com/mafredri/cdp/protocol/dom",
|
||||
"github.com/mafredri/cdp/rpcc",
|
||||
"github.com/pkg/errors",
|
||||
"github.com/sethgrid/pester",
|
||||
"github.com/smartystreets/goconvey/convey",
|
||||
"golang.org/x/sync/errgroup",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
46
Gopkg.toml
Normal file
46
Gopkg.toml
Normal file
@@ -0,0 +1,46 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/antlr/antlr4"
|
||||
version = "4.7.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mafredri/cdp"
|
||||
version = "0.19.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/chzyer/readline"
|
||||
version = "1.4"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/PuerkitoBio/goquery"
|
||||
version = "1.4.1"
|
50
Makefile
Normal file
50
Makefile
Normal file
@@ -0,0 +1,50 @@
|
||||
.PHONY: build install test doc fmt lint vet
|
||||
|
||||
export GOPATH
|
||||
|
||||
VERSION ?= $(shell git describe --tags --always --dirty)
|
||||
DIR_BIN = ./bin
|
||||
DIR_PKG = ./pkg
|
||||
DIR_CMD = ./cmd
|
||||
|
||||
default: build
|
||||
|
||||
build: install vet generate test compile
|
||||
|
||||
compile: compile_cli compile_server
|
||||
|
||||
compile_cli:
|
||||
go build -v -o ${DIR_BIN}/ferret \
|
||||
-ldflags "-X main.Version=${VERSION}" \
|
||||
${DIR_CMD}/cli/main.go
|
||||
|
||||
compile_server:
|
||||
go build -v -o ${DIR_BIN}/ferret_server \
|
||||
-ldflags "-X main.Version=${VERSION}" \
|
||||
${DIR_CMD}/server/main.go
|
||||
|
||||
install:
|
||||
dep ensure
|
||||
|
||||
test:
|
||||
go test ${DIR_PKG}/...
|
||||
|
||||
generate:
|
||||
go generate ${DIR_PKG}/...
|
||||
|
||||
doc:
|
||||
godoc -http=:6060 -index
|
||||
|
||||
# http://golang.org/cmd/go/#hdr-Run_gofmt_on_package_sources
|
||||
fmt:
|
||||
go fmt ${DIR_CMD}/... ${DIR_PKG}/...
|
||||
|
||||
# https://github.com/golang/lint
|
||||
# go get github.com/golang/lint/golint
|
||||
lint:
|
||||
golint ${DIR_CMD}/... ${DIR_PKG}/...
|
||||
|
||||
# http://godoc.org/code.google.com/p/go.tools/cmd/vet
|
||||
# go get code.google.com/p/go.tools/cmd/vet
|
||||
vet:
|
||||
go vet ${DIR_CMD}/... ${DIR_PKG}/...
|
85
README.md
85
README.md
@@ -1,2 +1,83 @@
|
||||
# ferret
|
||||
Specification
|
||||
# Ferret
|
||||
> Web scraping query language
|
||||
|
||||
## 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 the system where I realized how cumbersome writing tons of scrapers is.
|
||||
I was looking for some kind of tool that would let me to not write a code, but just express what data I need.
|
||||
Unfortunately, I didn't find anything, and therefore decided to create one.
|
||||
```Ferret``` project is an ambitious initiative 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 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 [ArrangoDB web site](https://docs.arangodb.com/3.3/AQL/index.html) and use AQL docs as a docs for FQL - since they are identical.
|
||||
|
||||
## Quick stark
|
||||
|
||||
### Browserless mode
|
||||
|
||||
If you want to play with ```fql``` and check its syntax, run CLI with the following commands:
|
||||
```
|
||||
go run ./cmd/cli/main.go
|
||||
|
||||
```
|
||||
|
||||
```ferret``` will run REPL.
|
||||
|
||||
```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:** blackslash is used for multiline queries.
|
||||
|
||||
If you want to execute a query store in a file, just type a file name
|
||||
|
||||
```
|
||||
go run ./cmd/cli/main.go ./docs/examples/hackernews.fql
|
||||
```
|
||||
|
||||
|
||||
### Browser mode
|
||||
|
||||
By default, ``ferret`` loads HTML pages via http protocol since 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 this case, you may fetch documents using Chrome or Chromium via Chrome DevTools protocol (aka CDP).
|
||||
|
||||
```shell
|
||||
go run ./cmd/cli/main.go --cdp-launch
|
||||
```
|
||||
|
||||
**Note:** Launch command is currently broken on MacOS.
|
||||
|
||||
Alternatively, you may open Chrome manually with ```remote-debugging-port=9222``` arguments and bass the address to ``ferret``:
|
||||
|
||||
```
|
||||
./bin/ferret --cdp http://127.0.0.1:9222
|
||||
```
|
||||
|
||||
In this case, you can use function ```DOCUMENT(url, isJsRendered)``` with ```true``` for loading JS rendered 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)
|
||||
>SLEEP(2000) // WAIT WHEN THE PAGE GETS RENDERED
|
||||
>LET tracks = ELEMENTS(doc, '.chartTrack__details')
|
||||
>LOG("found", LENGTH(tracks), "tracks")
|
||||
>FOR track IN tracks
|
||||
> LET username = ELEMENT(track, '.chartTrack__username')
|
||||
> LET title = ELEMENT(track, '.chartTrack__title')
|
||||
> RETURN {
|
||||
> artist: username.innerText,
|
||||
> track: title.innerText
|
||||
> }
|
||||
```
|
49
cmd/cli/app/exec.go
Normal file
49
cmd/cli/app/exec.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/MontFerret/ferret/pkg/compiler"
|
||||
"github.com/MontFerret/ferret/pkg/runtime"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
func Exec(pathToFile, cdpConn string) {
|
||||
query, err := ioutil.ReadFile(pathToFile)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
ferret := compiler.New()
|
||||
|
||||
prog, err := ferret.Compile(string(query))
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Failed to compile the query")
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
timer := NewTimer()
|
||||
timer.Start()
|
||||
|
||||
out, err := prog.Run(context.Background(), runtime.WithBrowser(cdpConn))
|
||||
|
||||
timer.Stop()
|
||||
|
||||
fmt.Println(timer.Print())
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Failed to execute the query")
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(string(out))
|
||||
}
|
79
cmd/cli/app/repl.go
Normal file
79
cmd/cli/app/repl.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/MontFerret/ferret/pkg/compiler"
|
||||
"github.com/MontFerret/ferret/pkg/runtime"
|
||||
"github.com/chzyer/readline"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Repl(version, cdpConn string) {
|
||||
ferret := compiler.New()
|
||||
|
||||
fmt.Printf("Welcome to Ferret REPL %s\n", version)
|
||||
fmt.Println("Please use `exit` or `Ctrl-D` to exit this program.")
|
||||
|
||||
rl, err := readline.NewEx(&readline.Config{
|
||||
Prompt: "> ",
|
||||
InterruptPrompt: "^C",
|
||||
EOFPrompt: "exit",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer rl.Close()
|
||||
|
||||
var commands []string
|
||||
timer := NewTimer()
|
||||
|
||||
for {
|
||||
line, err := rl.Readline()
|
||||
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasSuffix(line, "\\") {
|
||||
commands = append(commands, line[:len(line)-1])
|
||||
continue
|
||||
}
|
||||
|
||||
commands = append(commands, line)
|
||||
query := strings.Join(commands, "\n")
|
||||
|
||||
commands = make([]string, 0, 10)
|
||||
|
||||
program, err := ferret.Compile(query)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Failed to parse the query")
|
||||
fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
timer.Start()
|
||||
|
||||
out, err := program.Run(context.Background(), runtime.WithBrowser(cdpConn))
|
||||
|
||||
timer.Stop()
|
||||
fmt.Println(timer.Print())
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Failed to execute the query")
|
||||
fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Println(string(out))
|
||||
}
|
||||
}
|
27
cmd/cli/app/timer.go
Normal file
27
cmd/cli/app/timer.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Timer struct {
|
||||
start time.Time
|
||||
duration time.Duration
|
||||
}
|
||||
|
||||
func NewTimer() *Timer {
|
||||
return &Timer{}
|
||||
}
|
||||
|
||||
func (t *Timer) Start() {
|
||||
t.start = time.Now()
|
||||
}
|
||||
|
||||
func (t *Timer) Stop() {
|
||||
t.duration = time.Since(t.start)
|
||||
}
|
||||
|
||||
func (t *Timer) Print() string {
|
||||
return fmt.Sprintf("%f seconds", t.duration.Seconds())
|
||||
}
|
93
cmd/cli/main.go
Normal file
93
cmd/cli/main.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/MontFerret/ferret/cmd/cli/app"
|
||||
"github.com/MontFerret/ferret/pkg/browser"
|
||||
"os"
|
||||
)
|
||||
|
||||
var Version string
|
||||
|
||||
var (
|
||||
help = flag.Bool(
|
||||
"help",
|
||||
false,
|
||||
"show this list",
|
||||
)
|
||||
|
||||
version = flag.Bool(
|
||||
"version",
|
||||
false,
|
||||
"show REPL version",
|
||||
)
|
||||
|
||||
conn = flag.String(
|
||||
"cdp",
|
||||
"",
|
||||
"Chrome DevTools Protocol address",
|
||||
)
|
||||
|
||||
launchBrowser = flag.Bool(
|
||||
"cdp-launch",
|
||||
false,
|
||||
"launch Chrome",
|
||||
)
|
||||
|
||||
noUserData = flag.Bool(
|
||||
"no-user-data",
|
||||
false,
|
||||
"do not create a separate location for browser sessions",
|
||||
)
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if *help {
|
||||
flag.PrintDefaults()
|
||||
os.Exit(0)
|
||||
return
|
||||
}
|
||||
|
||||
if *version {
|
||||
fmt.Println(Version)
|
||||
os.Exit(0)
|
||||
return
|
||||
}
|
||||
|
||||
cdpConn := *conn
|
||||
|
||||
if cdpConn == "" && *launchBrowser {
|
||||
opts := make([]browser.Option, 0, 2)
|
||||
|
||||
//if *noUserData {
|
||||
// opts = append(opts, browser.WithoutUserDataDir())
|
||||
//}
|
||||
|
||||
// TODO: Make it optional.
|
||||
opts = append(opts, browser.WithoutUserDataDir())
|
||||
|
||||
// we need to launch Chrome instance
|
||||
b, err := browser.Launch(opts...)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(fmt.Sprintf("Failed to launch browser:"))
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cdpConn = b.DebuggingAddress()
|
||||
|
||||
defer b.Close()
|
||||
}
|
||||
|
||||
// no files to execute
|
||||
// run REPL
|
||||
if flag.NArg() == 0 {
|
||||
app.Repl(Version, cdpConn)
|
||||
} else {
|
||||
app.Exec(flag.Arg(0), cdpConn)
|
||||
}
|
||||
}
|
5
cmd/server/main.go
Normal file
5
cmd/server/main.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
|
||||
}
|
1
docs/README.md
Normal file
1
docs/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# Docs
|
4
docs/examples/hackernews.fql
Normal file
4
docs/examples/hackernews.fql
Normal file
@@ -0,0 +1,4 @@
|
||||
LET doc = DOCUMENT('https://news.ycombinator.com/')
|
||||
|
||||
FOR post IN ELEMENTS(doc, '.storylink')
|
||||
RETURN post.attributes.href
|
8
docs/examples/reddit.fql
Normal file
8
docs/examples/reddit.fql
Normal file
@@ -0,0 +1,8 @@
|
||||
LET reddit = DOCUMENT("https://www.reddit.com/")
|
||||
LET urls = ELEMENTS(reddit, 'a[data-click-id="body"]')
|
||||
|
||||
FOR url IN urls
|
||||
LET subreddit = DOCUMENT("https://www.reddit.com" + TRIM(url.attributes.href))
|
||||
LET post = ELEMENT(subreddit, 'div[data-test-id="post-content"] > div:nth-child(2) > div')
|
||||
|
||||
RETURN { title: post.children[0].innerText }
|
22
docs/examples/soundcloud.cdp.fql
Normal file
22
docs/examples/soundcloud.cdp.fql
Normal file
@@ -0,0 +1,22 @@
|
||||
LET doc = DOCUMENT('https://soundcloud.com/charts/top', true)
|
||||
|
||||
// TODO: We need a better way of waiting for page loading
|
||||
// Something line WAIT_FOR(doc, selector)
|
||||
SLEEP(2000)
|
||||
|
||||
LET tracks = ELEMENTS(doc, '.chartTrack__details')
|
||||
|
||||
LOG("found", LENGTH(tracks), "tracks")
|
||||
|
||||
FOR track IN tracks
|
||||
LET username = ELEMENT(track, '.chartTrack__username')
|
||||
LET title = ELEMENT(track, '.chartTrack__title')
|
||||
|
||||
// LOG("NODE", track.nodeName)
|
||||
|
||||
SLEEP(500)
|
||||
|
||||
RETURN {
|
||||
artist: username.innerText,
|
||||
track: title.innerText
|
||||
}
|
11
docs/fql/operations.md
Normal file
11
docs/fql/operations.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# High-level operations
|
||||
|
||||
The following high-level operations are described here after:
|
||||
|
||||
- [FOR]("operations/for.md"): Iterate over all elements of an array or an object.
|
||||
- [RETURN]("operations/return.md"): Produce the result of a query.
|
||||
- [FILTER]("operations/filter.md"): Restrict the results to elements that match arbitrary logical conditions.
|
||||
- [SORT]("operations/sort.md"): Force a sort of the array of already produced intermediate results.
|
||||
- [LIMIT]("operations/limit.md"): Reduce the number of elements in the result to at most the specified number, optionally skip elements (pagination).
|
||||
- [LET]("operations/let.md"): Assign an arbitrary value to a variable.
|
||||
- [COLLECT]("operations/collect.md"): Group an array by one or multiple group criteria. Can also count and aggregate.
|
210
docs/fql/operations/collect.md
Normal file
210
docs/fql/operations/collect.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# COLLECT
|
||||
The ```COLLECT``` keyword can be used to group an array by one or multiple group criteria.
|
||||
|
||||
The ```COLLECT``` statement will eliminate all local variables in the current scope. After ```COLLECT``` only the variables introduced by ```COLLECT``` itself are available.
|
||||
|
||||
The general syntaxes for ```COLLECT``` are:
|
||||
```
|
||||
COLLECT variableName = expression options
|
||||
COLLECT variableName = expression INTO groupsVariable options
|
||||
COLLECT variableName = expression INTO groupsVariable = projectionExpression options
|
||||
COLLECT variableName = expression INTO groupsVariable KEEP keepVariable options
|
||||
COLLECT variableName = expression WITH COUNT INTO countVariable options
|
||||
COLLECT variableName = expression AGGREGATE variableName = aggregateExpression options
|
||||
COLLECT AGGREGATE variableName = aggregateExpression options
|
||||
COLLECT WITH COUNT INTO countVariable options
|
||||
```
|
||||
|
||||
```options``` is optional in all variants.
|
||||
|
||||
## Grouping syntaxes
|
||||
The first syntax form of ```COLLECT``` only groups the result by the defined group criteria specified in expression. In order to further process the results produced by COLLECT, a new variable (specified by variableName) is introduced. This variable contains the group value.
|
||||
|
||||
Here's an example query that find the distinct values in u.city and makes them available in variable city:
|
||||
|
||||
```
|
||||
FOR u IN users
|
||||
```COLLECT``` city = u.city
|
||||
RETURN {
|
||||
"city" : city
|
||||
}
|
||||
```
|
||||
|
||||
The second form does the same as the first form, but additionally introduces a variable (specified by groupsVariable) that contains all elements that fell into the group. This works as follows: The groupsVariable variable is an array containing as many elements as there are in the group. Each member of that array is a JSON object in which the value of every variable that is defined in the AQL query is bound to the corresponding attribute. Note that this considers all variables that are defined before the ```COLLECT``` statement, but not those on the top level (outside of any FOR), unless the ```COLLECT``` statement is itself on the top level, in which case all variables are taken. Furthermore note that it is possible that the optimizer moves LET statements out of FOR statements to improve performance.
|
||||
|
||||
```
|
||||
FOR u IN users
|
||||
```COLLECT``` city = u.city INTO groups
|
||||
RETURN {
|
||||
"city" : city,
|
||||
"usersInCity" : groups
|
||||
}
|
||||
```
|
||||
|
||||
In the above example, the array users will be grouped by the attribute city. The result is a new array of documents, with one element per distinct u.city value. The elements from the original array (here: users) per city are made available in the variable groups. This is due to the INTO clause.
|
||||
|
||||
```COLLECT``` also allows specifying multiple group criteria. Individual group criteria can be separated by commas:
|
||||
|
||||
```
|
||||
FOR u IN users
|
||||
```COLLECT``` country = u.country, city = u.city INTO groups
|
||||
RETURN {
|
||||
"country" : country,
|
||||
"city" : city,
|
||||
"usersInCity" : groups
|
||||
}
|
||||
```
|
||||
|
||||
In the above example, the array users is grouped by country first and then by city, and for each distinct combination of country and city, the users will be returned.
|
||||
|
||||
## Discarding obsolete variables
|
||||
The third form of ```COLLECT``` allows rewriting the contents of the groupsVariable using an arbitrary projectionExpression:
|
||||
|
||||
```
|
||||
FOR u IN users
|
||||
```COLLECT``` country = u.country, city = u.city INTO groups = u.name
|
||||
RETURN {
|
||||
"country" : country,
|
||||
"city" : city,
|
||||
"userNames" : groups
|
||||
}
|
||||
```
|
||||
|
||||
In the above example, only the projectionExpression is u.name. Therefore, only this attribute is copied into the groupsVariable for each document. This is probably much more efficient than copying all variables from the scope into the groupsVariable as it would happen without a projectionExpression.
|
||||
|
||||
The expression following INTO can also be used for arbitrary computations:
|
||||
|
||||
```
|
||||
FOR u IN users
|
||||
```COLLECT``` country = u.country, city = u.city INTO groups = {
|
||||
"name" : u.name,
|
||||
"isActive" : u.status == "active"
|
||||
}
|
||||
RETURN {
|
||||
"country" : country,
|
||||
"city" : city,
|
||||
"usersInCity" : groups
|
||||
}
|
||||
```
|
||||
|
||||
```COLLECT``` also provides an optional KEEP clause that can be used to control which variables will be copied into the variable created by INTO. If no KEEP clause is specified, all variables from the scope will be copied as sub-attributes into the groupsVariable. This is safe but can have a negative impact on performance if there are many variables in scope or the variables contain massive amounts of data.
|
||||
|
||||
The following example limits the variables that are copied into the groupsVariable to just name. The variables u and someCalculation also present in the scope will not be copied into groupsVariable because they are not listed in the KEEP clause:
|
||||
|
||||
```
|
||||
FOR u IN users
|
||||
LET name = u.name
|
||||
LET someCalculation = u.value1 + u.value2
|
||||
```COLLECT``` city = u.city INTO groups KEEP name
|
||||
RETURN {
|
||||
"city" : city,
|
||||
"userNames" : groups[*].name
|
||||
}
|
||||
```
|
||||
|
||||
```KEEP``` is only valid in combination with INTO. Only valid variable names can be used in the KEEP clause. KEEP supports the specification of multiple variable names.
|
||||
|
||||
## Group length calculation
|
||||
```COLLECT``` also provides a special ```WITH COUNT``` clause that can be used to determine the number of group members efficiently.
|
||||
|
||||
The simplest form just returns the number of items that made it into the COLLECT:
|
||||
|
||||
```
|
||||
FOR u IN users
|
||||
```COLLECT``` WITH COUNT INTO length
|
||||
RETURN length
|
||||
```
|
||||
|
||||
The above is equivalent to, but less efficient than:
|
||||
|
||||
```
|
||||
RETURN LENGTH(users)
|
||||
```
|
||||
|
||||
The ```WITH COUNT``` clause can also be used to efficiently count the number of items in each group:
|
||||
|
||||
```
|
||||
FOR u IN users
|
||||
```COLLECT``` age = u.age WITH COUNT INTO length
|
||||
RETURN {
|
||||
"age" : age,
|
||||
"count" : length
|
||||
}
|
||||
```
|
||||
|
||||
Note: the ```WITH COUNT``` clause can only be used together with an INTO clause.
|
||||
|
||||
## Aggregation
|
||||
A ```COLLECT``` statement can be used to perform aggregation of data per group. To only determine group lengths, the WITH COUNT INTO variant of ```COLLECT``` can be used as described before.
|
||||
|
||||
For other aggregations, it is possible to run aggregate functions on the ```COLLECT``` results:
|
||||
|
||||
FOR u IN users
|
||||
```COLLECT``` ageGroup = FLOOR(u.age / 5) * 5 INTO g
|
||||
RETURN {
|
||||
"ageGroup" : ageGroup,
|
||||
"minAge" : MIN(g[*].u.age),
|
||||
"maxAge" : MAX(g[*].u.age)
|
||||
}
|
||||
The above however requires storing all group values during the collect operation for all groups, which can be inefficient.
|
||||
|
||||
The special AGGREGATE variant of ```COLLECT``` allows building the aggregate values incrementally during the collect operation, and is therefore often more efficient.
|
||||
|
||||
With the AGGREGATE variant the above query becomes:
|
||||
|
||||
FOR u IN users
|
||||
```COLLECT``` ageGroup = FLOOR(u.age / 5) * 5
|
||||
AGGREGATE minAge = MIN(u.age), maxAge = MAX(u.age)
|
||||
RETURN {
|
||||
ageGroup,
|
||||
minAge,
|
||||
maxAge
|
||||
}
|
||||
The AGGREGATE keyword can only be used after the ```COLLECT``` keyword. If used, it must directly follow the declaration of the grouping keys. If no grouping keys are used, it must follow the ```COLLECT``` keyword directly:
|
||||
|
||||
FOR u IN users
|
||||
```COLLECT``` AGGREGATE minAge = MIN(u.age), maxAge = MAX(u.age)
|
||||
RETURN {
|
||||
minAge,
|
||||
maxAge
|
||||
}
|
||||
Only specific expressions are allowed on the right-hand side of each AGGREGATE assignment:
|
||||
|
||||
on the top level, an aggregate expression must be a call to one of the supported aggregation functions LENGTH, MIN, MAX, SUM, AVERAGE, STDDEV_POPULATION, STDDEV_SAMPLE, VARIANCE_POPULATION, or VARIANCE_SAMPLE
|
||||
|
||||
an aggregate expression must not refer to variables introduced by the ```COLLECT``` itself
|
||||
|
||||
COLLECT variants
|
||||
Since ArangoDB 2.6, there are two variants of ```COLLECT``` that the optimizer can choose from: the sorted variant and the hash variant. The hash variant only becomes a candidate for ```COLLECT``` statements that do not use an INTO clause.
|
||||
|
||||
The optimizer will always generate a plan that employs the sorted method. The sorted method requires its input to be sorted by the group criteria specified in the ```COLLECT``` clause. To ensure correctness of the result, the AQL optimizer will automatically insert a SORT statement into the query in front of the ```COLLECT``` statement. The optimizer may be able to optimize away that SORT statement later if a sorted index is present on the group criteria.
|
||||
|
||||
In case a ```COLLECT``` qualifies for using the hash variant, the optimizer will create an extra plan for it at the beginning of the planning phase. In this plan, no extra SORT statement will be added in front of the COLLECT. This is because the hash variant of ```COLLECT``` does not require sorted input. Instead, a SORT statement will be added after the ```COLLECT``` to sort its output. This SORT statement may be optimized away again in later stages. If the sort order of the ```COLLECT``` is irrelevant to the user, adding the extra instruction SORT null after the ```COLLECT``` will allow the optimizer to remove the sorts altogether:
|
||||
|
||||
FOR u IN users
|
||||
```COLLECT``` age = u.age
|
||||
SORT null /* note: will be optimized away */
|
||||
RETURN age
|
||||
Which ```COLLECT``` variant is used by the optimizer depends on the optimizer's cost estimations. The created plans with the different ```COLLECT``` variants will be shipped through the regular optimization pipeline. In the end, the optimizer will pick the plan with the lowest estimated total cost as usual.
|
||||
|
||||
In general, the sorted variant of ```COLLECT``` should be preferred in cases when there is a sorted index present on the group criteria. In this case the optimizer can eliminate the SORT statement in front of the COLLECT, so that no SORT will be left.
|
||||
|
||||
If there is no sorted index available on the group criteria, the up-front sort required by the sorted variant can be expensive. In this case it is likely that the optimizer will prefer the hash variant of COLLECT, which does not require its input to be sorted.
|
||||
|
||||
Which variant of ```COLLECT``` was actually used can be figured out by looking into the execution plan of a query, specifically the AggregateNode and its aggregationOptions attribute.
|
||||
|
||||
Setting ```COLLECT``` options
|
||||
options can be used in a ```COLLECT``` statement to inform the optimizer about the preferred ```COLLECT``` method. When specifying the following appendix to a ```COLLECT``` statement, the optimizer will always use the sorted variant of ```COLLECT``` and not even create a plan using the hash variant:
|
||||
|
||||
OPTIONS { method: "sorted" }
|
||||
Note that specifying hash as method will not make the optimizer use the hash variant. This is because the hash variant is not eligible for all queries. Instead, if no options or any other method than sorted are specified in OPTIONS, the optimizer will use its regular cost estimations.
|
||||
|
||||
COLLECT vs. RETURN DISTINCT
|
||||
In order to make a result set unique, one can either use ```COLLECT``` or RETURN DISTINCT. Behind the scenes, both variants will work by creating an AggregateNode. For both variants, the optimizer may try the sorted and the hashed variant of COLLECT. The difference is therefore mainly syntactical, with RETURN DISTINCT saving a bit of typing when compared to an equivalent COLLECT:
|
||||
|
||||
FOR u IN users
|
||||
RETURN DISTINCT u.age
|
||||
FOR u IN users
|
||||
```COLLECT``` age = u.age
|
||||
RETURN age
|
||||
However, ```COLLECT``` is vastly more flexible than RETURN DISTINCT. Additionally, the order of results is undefined for a RETURN DISTINCT, whereas for a ```COLLECT``` the results will be sorted.
|
73
docs/fql/operations/filter.md
Normal file
73
docs/fql/operations/filter.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# FILTER
|
||||
The FILTER statement can be used to restrict the results to elements that match an arbitrary logical condition.
|
||||
|
||||
## General syntax
|
||||
|
||||
```
|
||||
FILTER condition
|
||||
```
|
||||
|
||||
```condition``` must be a condition that evaluates to either false or true. If the condition result is false, the current element is skipped, so it will not be processed further and not be part of the result.
|
||||
If the ```condition``` is true, the current element is not skipped and can be further processed.
|
||||
See Operators for a list of comparison operators, logical operators etc. that you can use in conditions.
|
||||
|
||||
```
|
||||
FOR u IN users
|
||||
FILTER u.active == true && u.age < 39
|
||||
RETURN u
|
||||
```
|
||||
|
||||
It is allowed to specify multiple FILTER statements in a query, even in the same block. If multiple FILTER statements are used, their results will be combined with a logical AND, meaning all filter conditions must be true to include an element.
|
||||
|
||||
```
|
||||
FOR u IN users
|
||||
FILTER u.active == true
|
||||
FILTER u.age < 39
|
||||
RETURN u
|
||||
```
|
||||
|
||||
In the above example, all array elements of users that have an attribute active with value true and that have an attribute age with a value less than 39 (including null ones) will be included in the result. All other elements of users will be skipped and not be included in the result produced by RETURN. You may refer to the chapter Accessing Data from Collections for a description of the impact of non-existent or null attributes.
|
||||
|
||||
Order of operations
|
||||
Note that the positions of FILTER statements can influence the result of a query. There are 16 active users in the test data for instance:
|
||||
|
||||
```
|
||||
FOR u IN users
|
||||
FILTER u.active == true
|
||||
RETURN u
|
||||
```
|
||||
|
||||
We can limit the result set to 5 users at most:
|
||||
|
||||
```
|
||||
FOR u IN users
|
||||
FILTER u.active == true
|
||||
LIMIT 5
|
||||
RETURN u
|
||||
```
|
||||
|
||||
This may return the user documents of Jim, Diego, Anthony, Michael and Chloe for instance. Which ones are returned is undefined, since there is no SORT statement to ensure a particular order. If we add a second FILTER statement to only return women...
|
||||
|
||||
```
|
||||
FOR u IN users
|
||||
FILTER u.active == true
|
||||
LIMIT 5
|
||||
FILTER u.gender == "f"
|
||||
RETURN u
|
||||
```
|
||||
... it might just return the Chloe document, because the LIMIT is applied before the second FILTER. No more than 5 documents arrive at the second FILTER block, and not all of them fulfill the gender criterion, eventhough there are more than 5 active female users in the collection. A more deterministic result can be achieved by adding a SORT block:
|
||||
|
||||
FOR u IN users
|
||||
FILTER u.active == true
|
||||
SORT u.age ASC
|
||||
LIMIT 5
|
||||
FILTER u.gender == "f"
|
||||
RETURN u
|
||||
This will return the users Mariah and Mary. If sorted by age in DESC order, then the Sophia, Emma and Madison documents are returned. A FILTER after a LIMIT is not very common however, and you probably want such a query instead:
|
||||
|
||||
FOR u IN users
|
||||
FILTER u.active == true AND u.gender == "f"
|
||||
SORT u.age ASC
|
||||
LIMIT 5
|
||||
RETURN u
|
||||
The significance of where FILTER blocks are placed allows that this single keyword can assume the roles of two SQL keywords, WHERE as well as HAVING. AQL's FILTER thus works with COLLECT aggregates the same as with any other intermediate result, document attribute etc.
|
43
docs/fql/operations/for.md
Normal file
43
docs/fql/operations/for.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# FOR
|
||||
|
||||
The FOR keyword can be to iterate over all elements of an array. The general syntax is:
|
||||
|
||||
```
|
||||
FOR variableName IN expression
|
||||
```
|
||||
|
||||
There is also a special variant for graph traversals:
|
||||
|
||||
```
|
||||
FOR valueVariableName, keyVariableName IN traversalExpression
|
||||
```
|
||||
|
||||
Each array element returned by expression is visited exactly once. It is required that expression returns an array in all cases. The empty array is allowed, too. The current array element is made available for further processing in the variable specified by variableName.
|
||||
|
||||
```
|
||||
FOR u IN users
|
||||
RETURN u
|
||||
```
|
||||
|
||||
This will iterate over all elements from the array users and make the current array element available in variable u.
|
||||
|
||||
The variable introduced by FOR is available until the scope the FOR is placed in is closed.
|
||||
|
||||
Another example that uses a statically declared array of values to iterate over:
|
||||
|
||||
```
|
||||
FOR year IN [ 2011, 2012, 2013 ]
|
||||
RETURN { "year" : year, "isLeapYear" : year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) }
|
||||
```
|
||||
|
||||
Nesting of multiple FOR statements is allowed, too. When FOR statements are nested, a cross product of the array elements returned by the individual FOR statements will be created.
|
||||
|
||||
```
|
||||
FOR u IN users
|
||||
FOR l IN locations
|
||||
RETURN { "user" : u, "location" : l }
|
||||
|
||||
```
|
||||
|
||||
In this example, there are two array iterations: an outer iteration over the array users plus an inner iteration over the array locations. The inner array is traversed as many times as there are elements in the outer array.
|
||||
For each iteration, the current values of users and locations are made available for further processing in the variable u and l.
|
167
docs/fql/operations/return.md
Normal file
167
docs/fql/operations/return.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# RETURN
|
||||
The RETURN statement can be used to produce the result of a query.
|
||||
It is mandatory to specify a RETURN statement at the end of each block in a data-selection query, otherwise the query result would be undefined.
|
||||
Using RETURN on the main level in data-modification queries is optional.
|
||||
|
||||
The general syntax for RETURN is:
|
||||
|
||||
```
|
||||
RETURN expression
|
||||
```
|
||||
|
||||
The expression returned by RETURN is produced for each iteration in the block the RETURN statement is placed in.
|
||||
That means the result of a RETURN statement is always an array. This includes an empty array if no documents matched the query and a single return value returned as array with one element.
|
||||
|
||||
To return all elements from the currently iterated array without modification, the following simple form can be used:
|
||||
|
||||
```
|
||||
FOR variableName IN expression
|
||||
RETURN variableName
|
||||
```
|
||||
|
||||
As RETURN allows specifying an expression, arbitrary computations can be performed to calculate the result elements.
|
||||
Any of the variables valid in the scope the RETURN is placed in can be used for the computations.
|
||||
|
||||
To iterate over all elements of an array, you can write:
|
||||
|
||||
```
|
||||
FOR u IN [{name: "Bob", age: 30}, {name: "Tom", age: 31}, {name: "Jeff"< age: 38}]
|
||||
RETURN u
|
||||
```
|
||||
|
||||
In each iteration of the for-loop, an element of the array is assigned to a variable u and returned unmodified in this example.
|
||||
To return only one attribute of each element, you could use a different return expression:
|
||||
|
||||
```
|
||||
FOR u IN [{name: "Bob", age: 30}, {name: "Tom", age: 31}, {name: "Jeff"< age: 38}]
|
||||
RETURN u.name
|
||||
```
|
||||
|
||||
Or to return multiple attributes, an object can be constructed like this:
|
||||
|
||||
```
|
||||
FOR u IN [{name: "Bob", age: 30}, {name: "Tom", age: 31}, {name: "Jeff"< age: 38}]
|
||||
RETURN { name: u.name, age: u.age }
|
||||
```
|
||||
|
||||
Note: RETURN will close the current scope and eliminate all local variables in it.
|
||||
This is important to remember when working with subqueries.
|
||||
|
||||
Dynamic attribute names are supported as well:
|
||||
|
||||
```
|
||||
FOR u IN [{id: "1", name: "Bob", age: 30}, {id: "2", name: "Tom", age: 31}, {id: "3", name: "Jeff"< age: 38}]
|
||||
RETURN { [ u.id ]: u.age }
|
||||
```
|
||||
|
||||
The document _id of every user is used as expression to compute the attribute key in this example:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"1": 30
|
||||
},
|
||||
{
|
||||
"2": 31
|
||||
},
|
||||
{
|
||||
"3": 38
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The result contains one object per user with a single key/value pair each.
|
||||
This is usually not desired.
|
||||
For a single object, that maps user IDs to ages, the individual results need to be merged and returned with another RETURN:
|
||||
|
||||
```
|
||||
RETURN MERGE(
|
||||
FOR u IN users
|
||||
RETURN { [ u._id ]: u.age }
|
||||
)
|
||||
```
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"users/10074": 69,
|
||||
"users/9883": 32,
|
||||
"users/9915": 27
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Keep in mind that if the key expression evaluates to the same value multiple times, only one of the key/value pairs with the duplicate name will survive MERGE(). To avoid this, you can go without dynamic attribute names, use static names instead and return all document properties as attribute values:
|
||||
|
||||
```
|
||||
FOR u IN users
|
||||
RETURN { name: u.name, age: u.age }
|
||||
```
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "John Smith",
|
||||
"age": 32
|
||||
},
|
||||
{
|
||||
"name": "James Hendrix",
|
||||
"age": 69
|
||||
},
|
||||
{
|
||||
"name": "Katie Foster",
|
||||
"age": 27
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## RETURN DISTINCT
|
||||
RETURN can optionally be followed by the DISTINCT keyword. The DISTINCT keyword will ensure uniqueness of the values returned by the RETURN statement:
|
||||
|
||||
```
|
||||
FOR variableName IN expression
|
||||
RETURN DISTINCT expression
|
||||
```
|
||||
|
||||
If the DISTINCT is applied on an expression that itself is an array or a subquery, the DISTINCT will not make the values in each array or subquery result unique, but instead ensure that the result contains only distinct arrays or subquery results. To make the result of an array or a subquery unique, simply apply the DISTINCT for the array or the subquery.
|
||||
|
||||
For example, the following query will apply DISTINCT on its subquery results, but not inside the subquery:
|
||||
|
||||
```
|
||||
FOR what IN 1..2
|
||||
RETURN DISTINCT (
|
||||
FOR i IN [ 1, 2, 3, 4, 1, 3 ]
|
||||
RETURN i
|
||||
)
|
||||
```
|
||||
|
||||
Here we'll have a FOR loop with two iterations that each execute a subquery. The DISTINCT here is applied on the two subquery results. Both subqueries return the same result value (that is [ 1, 2, 3, 4, 1, 3 ]), so after DISTINCT there will only be one occurrence of the value [ 1, 2, 3, 4, 1, 3 ] left:
|
||||
|
||||
```json
|
||||
[
|
||||
[ 1, 2, 3, 4, 1, 3 ]
|
||||
]
|
||||
```
|
||||
|
||||
If the goal is to apply the DISTINCT inside the subquery, it needs to be moved there:
|
||||
|
||||
```
|
||||
FOR what IN 1..2
|
||||
LET sub = (
|
||||
FOR i IN [ 1, 2, 3, 4, 1, 3 ]
|
||||
RETURN DISTINCT i
|
||||
)
|
||||
RETURN sub
|
||||
```
|
||||
|
||||
In the above case, the DISTINCT will make the subquery results unique, so that each subquery will return a unique array of values ([ 1, 2, 3, 4 ]). As the subquery is executed twice and there is no DISTINCT on the top-level, that array will be returned twice:
|
||||
|
||||
```json
|
||||
[
|
||||
[ 1, 2, 3, 4 ],
|
||||
[ 1, 2, 3, 4 ]
|
||||
]
|
||||
```
|
||||
|
||||
Note: the order of results was undefined for RETURN DISTINCT until before ArangoDB 3.3. Starting with ArangoDB 3.3, RETURN DISTINCT will not change the order of the results it is applied on.
|
||||
|
||||
Note: RETURN DISTINCT is not allowed on the top-level of a query if there is no FOR loop preceding it.
|
38
docs/fql/operations/sort.md
Normal file
38
docs/fql/operations/sort.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# SORT
|
||||
The SORT statement will force a sort of the array of already produced intermediate results in the current block. SORT allows specifying one or multiple sort criteria and directions. The general syntax is:
|
||||
|
||||
```
|
||||
SORT expression direction
|
||||
```
|
||||
|
||||
Example query that is sorting by lastName (in ascending order), then firstName (in ascending order), then by id (in descending order):
|
||||
|
||||
```
|
||||
FOR u IN users
|
||||
SORT u.lastName, u.firstName, u.id DESC
|
||||
RETURN u
|
||||
```
|
||||
|
||||
Specifying the direction is optional. The default (implicit) direction for a sort expression is the ascending order. To explicitly specify the sort direction, the keywords ASC (ascending) and DESC can be used. Multiple sort criteria can be separated using commas. In this case the direction is specified for each expression sperately. For example
|
||||
|
||||
```
|
||||
SORT doc.lastName, doc.firstName
|
||||
```
|
||||
|
||||
will first sort documents by lastName in ascending order and then by firstName in ascending order.
|
||||
|
||||
```
|
||||
SORT doc.lastName DESC, doc.firstName
|
||||
```
|
||||
|
||||
will first sort documents by lastName in descending order and then by firstName in ascending order.
|
||||
|
||||
```
|
||||
SORT doc.lastName, doc.firstName DESC
|
||||
```
|
||||
|
||||
will first sort documents by lastName in ascending order and then by firstName in descending order.
|
||||
|
||||
Note: when iterating over collection-based arrays, the order of documents is always undefined unless an explicit sort order is defined using SORT.
|
||||
|
||||
Note that constant SORT expressions can be used to indicate that no particular sort order is desired. Constant SORT expressions will be optimized away by the AQL optimizer during optimization, but specifying them explicitly may enable further optimizations if the optimizer does not need to take into account any particular sort order. This is especially the case after a COLLECT statement, which is supposed to produce a sorted result. Specifying an extra SORT null after the COLLECT statement allows to AQL optimizer to remove the post-sorting of the collect results altogether.
|
67
pkg/browser/browser.go
Normal file
67
pkg/browser/browser.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package browser
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type Browser struct {
|
||||
cmd *exec.Cmd
|
||||
flags Flags
|
||||
}
|
||||
|
||||
func (b *Browser) Flags() Flags {
|
||||
return b.flags
|
||||
}
|
||||
|
||||
func (b *Browser) DebuggingAddress() string {
|
||||
if !b.Flags().Has("remote-debugging-address") {
|
||||
b.Flags().Set("remote-debugging-address", "0.0.0.0")
|
||||
}
|
||||
|
||||
value, _ := b.Flags().Get("remote-debugging-address")
|
||||
|
||||
return value.(string)
|
||||
}
|
||||
|
||||
func (b *Browser) DebuggingPort() int {
|
||||
if !b.Flags().Has("remote-debugging-port") {
|
||||
b.Flags().Set("remote-debugging-port", 9222)
|
||||
}
|
||||
|
||||
value, _ := b.Flags().Get("remote-debugging-port")
|
||||
|
||||
return value.(int)
|
||||
}
|
||||
|
||||
func (b *Browser) Close() error {
|
||||
var err error
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
err = b.cmd.Process.Signal(os.Interrupt)
|
||||
} else {
|
||||
err = b.cmd.Process.Kill()
|
||||
}
|
||||
|
||||
_, err = b.cmd.Process.Wait()
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error waiting for process exit, result unknown")
|
||||
}
|
||||
|
||||
tmpDir, err := b.flags.GetString("user-data-dir")
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
os.RemoveAll(tmpDir)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
123
pkg/browser/flags.go
Normal file
123
pkg/browser/flags.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package browser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Flags map[string]interface{}
|
||||
|
||||
func (flags Flags) Get(arg string) (interface{}, error) {
|
||||
var values interface{}
|
||||
var err error
|
||||
|
||||
if !flags.Has(arg) {
|
||||
err = errors.Errorf("The specified argument '%s' does not exist", arg)
|
||||
} else {
|
||||
values = flags[arg]
|
||||
}
|
||||
|
||||
return values, err
|
||||
}
|
||||
|
||||
func (flags Flags) GetString(arg string) (string, error) {
|
||||
found, err := flags.Get(arg)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
str, ok := found.(string)
|
||||
|
||||
if ok {
|
||||
return str, nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (flags Flags) GetInt(arg string) (int, error) {
|
||||
found, err := flags.Get(arg)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
num, ok := found.(int)
|
||||
|
||||
if ok {
|
||||
return num, nil
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (flags Flags) Has(arg string) bool {
|
||||
_, exists := flags[arg]
|
||||
|
||||
return exists
|
||||
}
|
||||
|
||||
func (flags Flags) List() []string {
|
||||
var list []string
|
||||
|
||||
orderedFlags := make([]string, 0, 10)
|
||||
|
||||
for arg := range flags {
|
||||
orderedFlags = append(orderedFlags, arg)
|
||||
}
|
||||
|
||||
sort.Strings(orderedFlags)
|
||||
|
||||
for _, arg := range orderedFlags {
|
||||
val, err := flags.Get(arg)
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
switch val.(type) {
|
||||
case int:
|
||||
arg = fmt.Sprintf("--%s=%d", arg, val.(int))
|
||||
case string:
|
||||
arg = fmt.Sprintf("--%s=%s", arg, val.(string))
|
||||
default:
|
||||
arg = fmt.Sprintf("--%s", arg)
|
||||
}
|
||||
|
||||
list = append(list, arg)
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func (flags Flags) Set(arg string, value interface{}) (err error) {
|
||||
if value == nil {
|
||||
if _, ok := flags[arg]; !ok {
|
||||
flags[arg] = nil
|
||||
}
|
||||
}
|
||||
|
||||
if value != nil {
|
||||
switch value.(type) {
|
||||
case int:
|
||||
flags[arg] = value
|
||||
case string:
|
||||
flags[arg] = value
|
||||
default:
|
||||
return errors.Errorf("Invalid data type '%T' for argument %s: %+v", value, arg, value)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flags Flags) SetN(arg string) (err error) {
|
||||
return flags.Set(arg, nil)
|
||||
}
|
||||
|
||||
func (flags Flags) String() string {
|
||||
return strings.Join(flags.List(), " ")
|
||||
}
|
43
pkg/browser/helpers.go
Normal file
43
pkg/browser/helpers.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package browser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func resolveExecutablePath() string {
|
||||
var res string
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
for _, c := range []string{
|
||||
"/Applications/Google Chrome Canary.app",
|
||||
"/Applications/Google Chrome.app",
|
||||
} {
|
||||
// MacOS apps are actually folders
|
||||
if info, err := os.Stat(c); err == nil && info.IsDir() {
|
||||
res = fmt.Sprintf("open %s -n", c)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case "linux":
|
||||
for _, c := range []string{
|
||||
"headless_shell",
|
||||
"chromium",
|
||||
"google-chrome-beta",
|
||||
"google-chrome-unstable",
|
||||
"google-chrome-stable"} {
|
||||
if _, err := exec.LookPath(c); err == nil {
|
||||
res = c
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case "windows":
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
120
pkg/browser/launcher.go
Normal file
120
pkg/browser/launcher.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package browser
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func Launch(setters ...Option) (*Browser, error) {
|
||||
opts := &Options{
|
||||
headless: false,
|
||||
debuggingAddress: "0.0.0.0",
|
||||
debuggingPort: 9222,
|
||||
}
|
||||
|
||||
for _, setter := range setters {
|
||||
setter(opts)
|
||||
}
|
||||
|
||||
var flags Flags
|
||||
|
||||
if !opts.ignoreDefaultArgs {
|
||||
flags = DefaultFlags()
|
||||
} else {
|
||||
flags = Flags{}
|
||||
}
|
||||
|
||||
flags.Set("remote-debugging-port", opts.debuggingPort)
|
||||
|
||||
if opts.devtools {
|
||||
flags.SetN("auto-open-devtools-for-tabs")
|
||||
}
|
||||
|
||||
if opts.headless {
|
||||
flags.SetN("headless")
|
||||
flags.SetN("hide-scrollbars")
|
||||
flags.SetN("mute-audio")
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
flags.SetN("disable-gpu")
|
||||
}
|
||||
|
||||
temporaryUserDataDir := opts.userDataDir
|
||||
|
||||
if temporaryUserDataDir == "" && opts.noUserDataDir == false {
|
||||
dirName, err := ioutil.TempDir(os.TempDir(), "ferret_dev_profile-")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
temporaryUserDataDir = dirName
|
||||
}
|
||||
|
||||
workDir := filepath.Join(os.TempDir(), "ferret-chrome")
|
||||
|
||||
err := os.MkdirAll(workDir, 0700)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("cannot create working directory '%s'", workDir)
|
||||
}
|
||||
|
||||
if temporaryUserDataDir != "" {
|
||||
flags.Set("user-data-dir", temporaryUserDataDir)
|
||||
}
|
||||
|
||||
chromeExecutable := opts.executablePath
|
||||
|
||||
if chromeExecutable == "" {
|
||||
chromeExecutable = resolveExecutablePath()
|
||||
|
||||
if chromeExecutable == "" {
|
||||
return nil, errors.New("Chrome not found")
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command(chromeExecutable, flags.List()...)
|
||||
cmd.Dir = workDir
|
||||
|
||||
err = cmd.Start()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if opts.dumpio {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
}
|
||||
|
||||
return &Browser{cmd, flags}, nil
|
||||
}
|
||||
|
||||
func DefaultFlags() Flags {
|
||||
return Flags{
|
||||
"disable-background-networking": nil,
|
||||
"disable-background-timer-throttling": nil,
|
||||
"disable-breakpad": nil,
|
||||
"disable-client-side-phishing-detection": nil,
|
||||
"disable-default-apps": nil,
|
||||
"disable-dev-shm-usage": nil,
|
||||
"disable-extensions": nil,
|
||||
"disable-features=site-per-process": nil,
|
||||
"disable-hang-monitor": nil,
|
||||
"disable-popup-blocking": nil,
|
||||
"disable-prompt-on-repost": nil,
|
||||
"disable-sync": nil,
|
||||
"disable-translate": nil,
|
||||
"metrics-recording-only": nil,
|
||||
"no-first-run": nil,
|
||||
"safebrowsing-disable-auto-update": nil,
|
||||
"enable-automation": nil,
|
||||
"password-store=basic": nil,
|
||||
"use-mock-keychain": nil,
|
||||
}
|
||||
}
|
79
pkg/browser/options.go
Normal file
79
pkg/browser/options.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package browser
|
||||
|
||||
type (
|
||||
Option func(opts *Options)
|
||||
|
||||
Options struct {
|
||||
debuggingPort int
|
||||
debuggingAddress string
|
||||
ignoreDefaultArgs bool
|
||||
executablePath string
|
||||
ignoreHTTPSErrors bool
|
||||
slowMo bool
|
||||
dumpio bool
|
||||
headless bool
|
||||
devtools bool
|
||||
userDataDir string
|
||||
noUserDataDir bool
|
||||
}
|
||||
)
|
||||
|
||||
func WithoutDefaultArgs() Option {
|
||||
return func(opts *Options) {
|
||||
opts.ignoreDefaultArgs = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithCustomInstallation(executablePath string) Option {
|
||||
return func(opts *Options) {
|
||||
opts.executablePath = executablePath
|
||||
}
|
||||
}
|
||||
|
||||
func WithIgnoredHTTPSErrors() Option {
|
||||
return func(opts *Options) {
|
||||
opts.ignoreHTTPSErrors = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithSlowMo() Option {
|
||||
return func(opts *Options) {
|
||||
opts.slowMo = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithIO() Option {
|
||||
return func(opts *Options) {
|
||||
opts.dumpio = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithHeadless() Option {
|
||||
return func(opts *Options) {
|
||||
opts.headless = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithDevtools() Option {
|
||||
return func(opts *Options) {
|
||||
opts.devtools = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithDebugginPort(num int) Option {
|
||||
return func(opts *Options) {
|
||||
opts.debuggingPort = num
|
||||
}
|
||||
}
|
||||
|
||||
func WithUserDataDir(str string) Option {
|
||||
return func(opts *Options) {
|
||||
opts.userDataDir = str
|
||||
}
|
||||
}
|
||||
|
||||
func WithoutUserDataDir() Option {
|
||||
return func(opts *Options) {
|
||||
opts.noUserDataDir = true
|
||||
}
|
||||
}
|
68
pkg/compiler/compiler.go
Normal file
68
pkg/compiler/compiler.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"github.com/MontFerret/ferret/pkg/parser"
|
||||
"github.com/MontFerret/ferret/pkg/runtime"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type FqlCompiler struct {
|
||||
funcs map[string]core.Function
|
||||
}
|
||||
|
||||
func New() *FqlCompiler {
|
||||
return &FqlCompiler{
|
||||
stdlib.NewLib(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *FqlCompiler) RegisterFunction(name string, fun core.Function) error {
|
||||
_, exists := c.funcs[name]
|
||||
|
||||
if exists {
|
||||
return errors.Errorf("function already exists: %s", name)
|
||||
}
|
||||
|
||||
c.funcs[name] = fun
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *FqlCompiler) Compile(query string) (program *runtime.Program, err error) {
|
||||
if query == "" {
|
||||
return nil, ErrEmptyQuery
|
||||
}
|
||||
|
||||
p := parser.New(query)
|
||||
p.AddErrorListener(&errorListener{})
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// find out exactly what the error was and set err
|
||||
switch x := r.(type) {
|
||||
case string:
|
||||
err = errors.New(x)
|
||||
case error:
|
||||
err = x
|
||||
default:
|
||||
err = errors.New("unknown panic")
|
||||
}
|
||||
|
||||
program = nil
|
||||
}
|
||||
}()
|
||||
|
||||
l := newVisitor(c.funcs)
|
||||
|
||||
res := p.Visit(l).(*result)
|
||||
|
||||
if res.Ok() {
|
||||
program = res.Data().(*runtime.Program)
|
||||
} else {
|
||||
err = res.Error()
|
||||
}
|
||||
|
||||
return program, err
|
||||
}
|
1355
pkg/compiler/compiler_test.go
Normal file
1355
pkg/compiler/compiler_test.go
Normal file
File diff suppressed because it is too large
Load Diff
12
pkg/compiler/errors.go
Normal file
12
pkg/compiler/errors.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package compiler
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
var (
|
||||
ErrEmptyQuery = errors.New("empty query")
|
||||
ErrNotImplemented = errors.New("not implemented")
|
||||
ErrVariableNotFound = errors.New("variable not found")
|
||||
ErrVariableNotUnique = errors.New("variable is already defined")
|
||||
ErrInvalidToken = errors.New("invalid token")
|
||||
ErrInvalidDataSource = errors.New("invalid data source")
|
||||
)
|
14
pkg/compiler/listener.go
Normal file
14
pkg/compiler/listener.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"github.com/antlr/antlr4/runtime/Go/antlr"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type errorListener struct {
|
||||
*antlr.DefaultErrorListener
|
||||
}
|
||||
|
||||
func (d *errorListener) SyntaxError(recognizer antlr.Recognizer, offendingSymbol interface{}, line, column int, msg string, e antlr.RecognitionException) {
|
||||
panic(errors.Errorf("%s at %d:%d", msg, line, column))
|
||||
}
|
30
pkg/compiler/result.go
Normal file
30
pkg/compiler/result.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package compiler
|
||||
|
||||
type visitorFn func() (interface{}, error)
|
||||
|
||||
type result struct {
|
||||
data interface{}
|
||||
err error
|
||||
}
|
||||
|
||||
func newResult(data interface{}, err error) *result {
|
||||
return &result{data, err}
|
||||
}
|
||||
|
||||
func newResultFrom(fn visitorFn) *result {
|
||||
out, err := fn()
|
||||
|
||||
return &result{out, err}
|
||||
}
|
||||
|
||||
func (res *result) Ok() bool {
|
||||
return res.err == nil
|
||||
}
|
||||
|
||||
func (res *result) Data() interface{} {
|
||||
return res.data
|
||||
}
|
||||
|
||||
func (res *result) Error() error {
|
||||
return res.err
|
||||
}
|
63
pkg/compiler/scope.go
Normal file
63
pkg/compiler/scope.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
scope struct {
|
||||
parent *scope
|
||||
vars map[string]core.Type
|
||||
}
|
||||
)
|
||||
|
||||
func newRootScope() *scope {
|
||||
return &scope{
|
||||
vars: make(map[string]core.Type),
|
||||
}
|
||||
}
|
||||
|
||||
func newScope(parent *scope) *scope {
|
||||
s := newRootScope()
|
||||
s.parent = parent
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *scope) GetVariable(name string) (core.Type, error) {
|
||||
local, exists := s.vars[name]
|
||||
|
||||
if exists {
|
||||
return local, nil
|
||||
}
|
||||
|
||||
if s.parent != nil {
|
||||
parents, err := s.parent.GetVariable(name)
|
||||
|
||||
if err != nil {
|
||||
return core.NoneType, err
|
||||
}
|
||||
|
||||
return parents, nil
|
||||
}
|
||||
|
||||
return core.NoneType, core.Error(ErrVariableNotFound, name)
|
||||
}
|
||||
|
||||
func (s *scope) SetVariable(name string) error {
|
||||
_, exists := s.vars[name]
|
||||
|
||||
if exists {
|
||||
return errors.Wrap(ErrVariableNotUnique, name)
|
||||
}
|
||||
|
||||
// TODO: add type detection
|
||||
s.vars[name] = core.NoneType
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scope) Fork() *scope {
|
||||
return newScope(s)
|
||||
}
|
900
pkg/compiler/visitor.go
Normal file
900
pkg/compiler/visitor.go
Normal file
@@ -0,0 +1,900 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/MontFerret/ferret/pkg/parser/fql"
|
||||
"github.com/MontFerret/ferret/pkg/runtime"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/expressions"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/expressions/clauses"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/expressions/literals"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/expressions/operators"
|
||||
"github.com/antlr/antlr4/runtime/Go/antlr"
|
||||
"github.com/pkg/errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type visitor struct {
|
||||
*fql.BaseFqlParserVisitor
|
||||
|
||||
funcs map[string]core.Function
|
||||
}
|
||||
|
||||
func newVisitor(funcs map[string]core.Function) *visitor {
|
||||
return &visitor{
|
||||
&fql.BaseFqlParserVisitor{},
|
||||
funcs,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *visitor) VisitProgram(ctx *fql.ProgramContext) interface{} {
|
||||
return newResultFrom(func() (interface{}, error) {
|
||||
rootScope := newRootScope()
|
||||
block, err := v.doVisitBody(ctx.Body().(*fql.BodyContext), rootScope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return runtime.NewProgram(block), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitBody(ctx *fql.BodyContext, scope *scope) (core.Expression, error) {
|
||||
statements := ctx.AllBodyStatement()
|
||||
body := expressions.NewBlockExpression(len(statements) + 1)
|
||||
|
||||
for _, stmt := range statements {
|
||||
e, err := v.doVisitBodyStatement(stmt.(*fql.BodyStatementContext), scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body.Add(e)
|
||||
}
|
||||
|
||||
exp := ctx.BodyExpression()
|
||||
|
||||
if exp != nil {
|
||||
e, err := v.doVisitBodyExpression(exp.(*fql.BodyExpressionContext), scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if e != nil {
|
||||
body.Add(e)
|
||||
}
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitBodyStatement(ctx *fql.BodyStatementContext, scope *scope) (core.Expression, error) {
|
||||
variable := ctx.VariableDeclaration()
|
||||
|
||||
if variable != nil {
|
||||
return v.doVisitVariableDeclaration(variable.(*fql.VariableDeclarationContext), scope)
|
||||
}
|
||||
|
||||
funcCall := ctx.FunctionCallExpression()
|
||||
|
||||
if funcCall != nil {
|
||||
return v.doVisitFunctionCallExpression(funcCall.(*fql.FunctionCallExpressionContext), scope)
|
||||
}
|
||||
|
||||
return nil, errors.Wrap(ErrInvalidToken, ctx.GetText())
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitBodyExpression(ctx *fql.BodyExpressionContext, scope *scope) (core.Expression, error) {
|
||||
forIn := ctx.ForExpression()
|
||||
|
||||
if forIn != nil {
|
||||
return v.doVisitForExpression(forIn.(*fql.ForExpressionContext), scope)
|
||||
}
|
||||
|
||||
ret := ctx.ReturnExpression()
|
||||
|
||||
if ret != nil {
|
||||
return v.doVisitReturnExpression(ret.(*fql.ReturnExpressionContext), scope)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitReturnExpression(ctx *fql.ReturnExpressionContext, scope *scope) (core.Expression, error) {
|
||||
var exp core.Expression
|
||||
expCtx := ctx.Expression()
|
||||
|
||||
if expCtx != nil {
|
||||
out, err := v.doVisitExpression(expCtx.(*fql.ExpressionContext), scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exp = out
|
||||
} else {
|
||||
out, err := v.doVisitForExpression(ctx.ForExpression().(*fql.ForExpressionContext), scope.Fork())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exp = out
|
||||
}
|
||||
|
||||
return expressions.NewReturnExpression(v.getSourceMap(ctx), exp)
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitForExpression(ctx *fql.ForExpressionContext, scope *scope) (core.Expression, error) {
|
||||
var valVarName string
|
||||
var keyVarName string
|
||||
|
||||
valVar := ctx.ForExpressionValueVariable()
|
||||
valVarName = valVar.GetText()
|
||||
forInScope := scope.Fork()
|
||||
forInScope.SetVariable(valVarName)
|
||||
|
||||
keyVar := ctx.ForExpressionKeyVariable()
|
||||
|
||||
if keyVar != nil {
|
||||
keyVarName = keyVar.GetText()
|
||||
forInScope.SetVariable(keyVarName)
|
||||
}
|
||||
|
||||
src, err := v.doVisitForExpressionSource(ctx.ForExpressionSource().(*fql.ForExpressionSourceContext), forInScope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body := ctx.AllForExpressionBody()
|
||||
predicate := expressions.NewBlockExpression(len(body) + 1)
|
||||
|
||||
for _, el := range body {
|
||||
el, err := v.doVisitForExpressionBody(el.(*fql.ForExpressionBodyContext), forInScope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = predicate.Add(el)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var spread bool
|
||||
var distinct bool
|
||||
forRetCtx := ctx.ForExpressionReturn().(*fql.ForExpressionReturnContext)
|
||||
returnCtx := forRetCtx.ReturnExpression()
|
||||
|
||||
if returnCtx != nil {
|
||||
returnCtx := returnCtx.(*fql.ReturnExpressionContext)
|
||||
returnExp, err := v.doVisitReturnExpression(returnCtx, forInScope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
distinct = returnCtx.Distinct() != nil
|
||||
|
||||
predicate.Add(returnExp)
|
||||
} else {
|
||||
forInCtx := forRetCtx.ForExpression().(*fql.ForExpressionContext)
|
||||
forInExp, err := v.doVisitForExpression(forInCtx, forInScope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spread = true
|
||||
|
||||
predicate.Add(forInExp)
|
||||
}
|
||||
|
||||
forExp, err := expressions.NewForExpression(
|
||||
v.getSourceMap(ctx),
|
||||
valVarName,
|
||||
keyVarName,
|
||||
src,
|
||||
predicate,
|
||||
distinct,
|
||||
spread,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, clause := range ctx.AllForExpressionClause() {
|
||||
clause := clause.(*fql.ForExpressionClauseContext)
|
||||
|
||||
limitCtx := clause.LimitClause()
|
||||
|
||||
if limitCtx != nil {
|
||||
limit, offset, err := v.createLimit(limitCtx.(*fql.LimitClauseContext))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
forExp.AddLimit(v.getSourceMap(limitCtx), limit, offset)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
filterCtx := clause.FilterClause()
|
||||
|
||||
if filterCtx != nil {
|
||||
filterExp, err := v.createFilter(filterCtx.(*fql.FilterClauseContext), forInScope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
forExp.AddFilter(v.getSourceMap(filterCtx), filterExp)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
sortCtx := clause.SortClause()
|
||||
|
||||
if sortCtx != nil {
|
||||
sortCtx := sortCtx.(*fql.SortClauseContext)
|
||||
sortExps, err := v.createSort(sortCtx, forInScope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
forExp.AddSort(v.getSourceMap(sortCtx), sortExps...)
|
||||
}
|
||||
}
|
||||
|
||||
return forExp, nil
|
||||
}
|
||||
|
||||
func (v *visitor) createLimit(ctx *fql.LimitClauseContext) (int, int, error) {
|
||||
var limit int
|
||||
var offset int
|
||||
|
||||
intLiterals := ctx.AllIntegerLiteral()
|
||||
|
||||
limitLiteral := intLiterals[0]
|
||||
limit, err := strconv.Atoi(limitLiteral.GetText())
|
||||
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
if len(intLiterals) > 1 {
|
||||
offsetLiteral := intLiterals[1]
|
||||
|
||||
offset, err = strconv.Atoi(offsetLiteral.GetText())
|
||||
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return limit, offset, nil
|
||||
}
|
||||
|
||||
func (v *visitor) createFilter(ctx *fql.FilterClauseContext, scope *scope) (core.Expression, error) {
|
||||
exp := ctx.Expression().(*fql.ExpressionContext)
|
||||
|
||||
exps, err := v.doVisitAllExpressions(exp.AllExpression(), scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
left := exps[0]
|
||||
right := exps[1]
|
||||
|
||||
equalityOp := exp.EqualityOperator()
|
||||
|
||||
if equalityOp != nil {
|
||||
return operators.NewEqualityOperator(v.getSourceMap(ctx), left, right, equalityOp.GetText())
|
||||
}
|
||||
|
||||
logicalOp := exp.LogicalOperator()
|
||||
|
||||
if logicalOp != nil {
|
||||
return operators.NewLogicalOperator(v.getSourceMap(ctx), left, right, logicalOp.GetText())
|
||||
}
|
||||
|
||||
return nil, core.Error(ErrInvalidToken, ctx.GetText())
|
||||
}
|
||||
|
||||
func (v *visitor) createSort(ctx *fql.SortClauseContext, scope *scope) ([]*clauses.SorterExpression, error) {
|
||||
sortExpCtxs := ctx.AllSortClauseExpression()
|
||||
|
||||
res := make([]*clauses.SorterExpression, len(sortExpCtxs))
|
||||
|
||||
for idx, sortExpCtx := range sortExpCtxs {
|
||||
sortExpCtx := sortExpCtx.(*fql.SortClauseExpressionContext)
|
||||
exp, err := v.doVisitExpression(sortExpCtx.Expression().(*fql.ExpressionContext), scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
direction := collections.SortDirectionAsc
|
||||
dir := sortExpCtx.SortDirection()
|
||||
|
||||
if dir != nil {
|
||||
direction = collections.SortDirectionFromString(dir.GetText())
|
||||
}
|
||||
|
||||
sorterExp, err := clauses.NewSorterExpression(
|
||||
exp,
|
||||
direction,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res[idx] = sorterExp
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitForExpressionSource(ctx *fql.ForExpressionSourceContext, scope *scope) (collections.IterableExpression, error) {
|
||||
arr := ctx.ArrayLiteral()
|
||||
|
||||
if arr != nil {
|
||||
return v.doVisitArrayLiteral(arr.(*fql.ArrayLiteralContext), scope)
|
||||
}
|
||||
|
||||
obj := ctx.ObjectLiteral()
|
||||
|
||||
if obj != nil {
|
||||
return v.doVisitObjectLiteral(obj.(*fql.ObjectLiteralContext), scope)
|
||||
}
|
||||
|
||||
variable := ctx.Variable()
|
||||
|
||||
if variable != nil {
|
||||
return v.doVisitVariable(variable.(*fql.VariableContext), scope)
|
||||
}
|
||||
|
||||
funcCall := ctx.FunctionCallExpression()
|
||||
|
||||
if funcCall != nil {
|
||||
return v.doVisitFunctionCallExpression(funcCall.(*fql.FunctionCallExpressionContext), scope)
|
||||
}
|
||||
|
||||
memberExp := ctx.MemberExpression()
|
||||
|
||||
if memberExp != nil {
|
||||
return v.doVisitMemberExpression(memberExp.(*fql.MemberExpressionContext), scope)
|
||||
}
|
||||
|
||||
return nil, core.Error(ErrInvalidDataSource, ctx.GetText())
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitForExpressionBody(ctx *fql.ForExpressionBodyContext, scope *scope) (core.Expression, error) {
|
||||
varDecCtx := ctx.VariableDeclaration()
|
||||
|
||||
if varDecCtx != nil {
|
||||
return v.doVisitVariableDeclaration(varDecCtx.(*fql.VariableDeclarationContext), scope)
|
||||
}
|
||||
|
||||
funcCallCtx := ctx.FunctionCallExpression()
|
||||
|
||||
if funcCallCtx != nil {
|
||||
return v.doVisitFunctionCallExpression(funcCallCtx.(*fql.FunctionCallExpressionContext), scope)
|
||||
}
|
||||
|
||||
return nil, v.unexpectedToken(ctx)
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitMemberExpression(ctx *fql.MemberExpressionContext, scope *scope) (collections.IterableExpression, error) {
|
||||
varName := ctx.Identifier().GetText()
|
||||
|
||||
_, err := scope.GetVariable(varName)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
children := ctx.GetChildren()
|
||||
path := make([]core.Expression, 0, len(children))
|
||||
|
||||
for _, child := range children {
|
||||
_, ok := child.(antlr.TerminalNode)
|
||||
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
|
||||
var exp core.Expression
|
||||
var err error
|
||||
var parsed bool
|
||||
|
||||
prop, ok := child.(*fql.PropertyNameContext)
|
||||
|
||||
if ok {
|
||||
exp, err = v.doVisitPropertyNameContext(prop, scope)
|
||||
parsed = true
|
||||
} else {
|
||||
computedProp, ok := child.(*fql.ComputedPropertyNameContext)
|
||||
|
||||
if ok {
|
||||
exp, err = v.doVisitComputedPropertyNameContext(computedProp, scope)
|
||||
parsed = true
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !parsed {
|
||||
// TODO: add more contextual information
|
||||
return nil, ErrInvalidToken
|
||||
}
|
||||
|
||||
path = append(path, exp)
|
||||
}
|
||||
|
||||
member, err := expressions.NewMemberExpression(
|
||||
v.getSourceMap(ctx),
|
||||
varName,
|
||||
path,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return member, nil
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitObjectLiteral(ctx *fql.ObjectLiteralContext, scope *scope) (collections.IterableExpression, error) {
|
||||
assignments := ctx.AllPropertyAssignment()
|
||||
props := make([]*literals.ObjectPropertyAssignment, 0, len(assignments))
|
||||
|
||||
for _, assignment := range assignments {
|
||||
var name core.Expression
|
||||
var value core.Expression
|
||||
var err error
|
||||
|
||||
assignment := assignment.(*fql.PropertyAssignmentContext)
|
||||
|
||||
prop := assignment.PropertyName()
|
||||
computedProp := assignment.ComputedPropertyName()
|
||||
shortHand := assignment.ShorthandPropertyName()
|
||||
|
||||
if prop != nil {
|
||||
name, err = v.doVisitPropertyNameContext(prop.(*fql.PropertyNameContext), scope)
|
||||
} else if computedProp != nil {
|
||||
name, err = v.doVisitComputedPropertyNameContext(computedProp.(*fql.ComputedPropertyNameContext), scope)
|
||||
} else {
|
||||
name, err = v.doVisitShorthandPropertyNameContext(shortHand.(*fql.ShorthandPropertyNameContext), scope)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if shortHand == nil {
|
||||
value, err = v.visit(assignment.Expression(), scope)
|
||||
} else {
|
||||
value, err = v.doVisitVariable(shortHand.(*fql.ShorthandPropertyNameContext).Variable().(*fql.VariableContext), scope)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
props = append(props, literals.NewObjectPropertyAssignment(name, value))
|
||||
}
|
||||
|
||||
return literals.NewObjectLiteralWith(props...), nil
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitPropertyNameContext(ctx *fql.PropertyNameContext, scope *scope) (core.Expression, error) {
|
||||
return literals.NewStringLiteral(ctx.Identifier().GetText()), nil
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitComputedPropertyNameContext(ctx *fql.ComputedPropertyNameContext, scope *scope) (core.Expression, error) {
|
||||
return v.doVisitExpression(ctx.Expression().(*fql.ExpressionContext), scope)
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitShorthandPropertyNameContext(ctx *fql.ShorthandPropertyNameContext, scope *scope) (core.Expression, error) {
|
||||
name := ctx.Variable().GetText()
|
||||
|
||||
_, err := scope.GetVariable(name)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return literals.NewStringLiteral(ctx.Variable().GetText()), nil
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitArrayLiteral(ctx *fql.ArrayLiteralContext, scope *scope) (collections.IterableExpression, error) {
|
||||
listCtx := ctx.ArrayElementList()
|
||||
|
||||
if listCtx == nil {
|
||||
return literals.NewArrayLiteral(0), nil
|
||||
}
|
||||
|
||||
list := listCtx.(*fql.ArrayElementListContext)
|
||||
exp := list.AllExpression()
|
||||
elements := make([]core.Expression, 0, len(exp))
|
||||
|
||||
for _, e := range exp {
|
||||
element, err := v.visit(e, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
elements = append(elements, element)
|
||||
}
|
||||
|
||||
return literals.NewArrayLiteralWith(elements...), nil
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitFloatLiteral(ctx *fql.FloatLiteralContext) (core.Expression, error) {
|
||||
val, err := strconv.ParseFloat(ctx.GetText(), 64)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return literals.NewFloatLiteral(val), nil
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitIntegerLiteral(ctx *fql.IntegerLiteralContext) (core.Expression, error) {
|
||||
val, err := strconv.Atoi(ctx.GetText())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return literals.NewIntLiteral(val), nil
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitStringLiteral(ctx *fql.StringLiteralContext) (core.Expression, error) {
|
||||
text := ctx.StringLiteral().GetText()
|
||||
|
||||
// remove extra quotes
|
||||
return literals.NewStringLiteral(text[1 : len(text)-1]), nil
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitBooleanLiteral(ctx *fql.BooleanLiteralContext) (core.Expression, error) {
|
||||
return literals.NewBooleanLiteral(strings.ToUpper(ctx.GetText()) == "TRUE"), nil
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitNoneLiteral(ctx *fql.NoneLiteralContext) (core.Expression, error) {
|
||||
return literals.None, nil
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitVariable(ctx *fql.VariableContext, scope *scope) (collections.IterableExpression, error) {
|
||||
name := ctx.Identifier().GetText()
|
||||
|
||||
// check whether the variable is defined
|
||||
_, err := scope.GetVariable(name)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return expressions.NewVariableExpression(v.getSourceMap(ctx), name)
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitVariableDeclaration(ctx *fql.VariableDeclarationContext, scope *scope) (core.Expression, error) {
|
||||
var init core.Expression
|
||||
var err error
|
||||
|
||||
exp := ctx.Expression()
|
||||
|
||||
if exp != nil {
|
||||
init, err = v.doVisitExpression(ctx.Expression().(*fql.ExpressionContext), scope)
|
||||
} else {
|
||||
forIn := ctx.ForExpression()
|
||||
|
||||
if forIn != nil {
|
||||
init, err = v.doVisitForExpression(forIn.(*fql.ForExpressionContext), scope)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := ctx.Identifier().GetText()
|
||||
|
||||
scope.SetVariable(name)
|
||||
|
||||
return expressions.NewVariableDeclarationExpression(
|
||||
v.getSourceMap(ctx),
|
||||
name,
|
||||
init,
|
||||
)
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitChildren(node antlr.RuleNode, scope *scope) ([]core.Expression, error) {
|
||||
children := node.GetChildren()
|
||||
|
||||
if children == nil {
|
||||
return make([]core.Expression, 0, 0), nil
|
||||
}
|
||||
|
||||
result := make([]core.Expression, len(children))
|
||||
|
||||
for idx, child := range children {
|
||||
out, err := v.visit(child, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result[idx] = out
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitExpression(ctx *fql.ExpressionContext, scope *scope) (core.Expression, error) {
|
||||
variable := ctx.Variable()
|
||||
|
||||
if variable != nil {
|
||||
return v.doVisitVariable(variable.(*fql.VariableContext), scope)
|
||||
}
|
||||
|
||||
str := ctx.StringLiteral()
|
||||
|
||||
if str != nil {
|
||||
return v.doVisitStringLiteral(str.(*fql.StringLiteralContext))
|
||||
}
|
||||
|
||||
integ := ctx.IntegerLiteral()
|
||||
|
||||
if integ != nil {
|
||||
return v.doVisitIntegerLiteral(integ.(*fql.IntegerLiteralContext))
|
||||
}
|
||||
|
||||
float := ctx.FloatLiteral()
|
||||
|
||||
if float != nil {
|
||||
return v.doVisitFloatLiteral(float.(*fql.FloatLiteralContext))
|
||||
}
|
||||
|
||||
boolean := ctx.BooleanLiteral()
|
||||
|
||||
if boolean != nil {
|
||||
return v.doVisitBooleanLiteral(boolean.(*fql.BooleanLiteralContext))
|
||||
}
|
||||
|
||||
arr := ctx.ArrayLiteral()
|
||||
|
||||
if arr != nil {
|
||||
return v.doVisitArrayLiteral(arr.(*fql.ArrayLiteralContext), scope)
|
||||
}
|
||||
|
||||
obj := ctx.ObjectLiteral()
|
||||
|
||||
if obj != nil {
|
||||
return v.doVisitObjectLiteral(obj.(*fql.ObjectLiteralContext), scope)
|
||||
}
|
||||
|
||||
funCall := ctx.FunctionCallExpression()
|
||||
|
||||
if funCall != nil {
|
||||
return v.doVisitFunctionCallExpression(funCall.(*fql.FunctionCallExpressionContext), scope)
|
||||
}
|
||||
|
||||
member := ctx.MemberExpression()
|
||||
|
||||
if member != nil {
|
||||
return v.doVisitMemberExpression(member.(*fql.MemberExpressionContext), scope)
|
||||
}
|
||||
|
||||
none := ctx.NoneLiteral()
|
||||
|
||||
if none != nil {
|
||||
return v.doVisitNoneLiteral(none.(*fql.NoneLiteralContext))
|
||||
}
|
||||
|
||||
equalityOp := ctx.EqualityOperator()
|
||||
|
||||
if equalityOp != nil {
|
||||
equalityOp := equalityOp.(*fql.EqualityOperatorContext)
|
||||
exps, err := v.doVisitAllExpressions(ctx.AllExpression(), scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
left := exps[0]
|
||||
right := exps[1]
|
||||
|
||||
return operators.NewEqualityOperator(v.getSourceMap(equalityOp), left, right, equalityOp.GetText())
|
||||
}
|
||||
|
||||
logicalOp := ctx.LogicalOperator()
|
||||
|
||||
if logicalOp != nil {
|
||||
logicalOp := logicalOp.(*fql.LogicalOperatorContext)
|
||||
exps, err := v.doVisitAllExpressions(ctx.AllExpression(), scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
left := exps[0]
|
||||
right := exps[1]
|
||||
|
||||
return operators.NewLogicalOperator(v.getSourceMap(logicalOp), left, right, logicalOp.GetText())
|
||||
}
|
||||
|
||||
mathOp := ctx.MathOperator()
|
||||
|
||||
if mathOp != nil {
|
||||
mathOp := mathOp.(*fql.MathOperatorContext)
|
||||
exps, err := v.doVisitAllExpressions(ctx.AllExpression(), scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
left := exps[0]
|
||||
right := exps[1]
|
||||
|
||||
return operators.NewMathOperator(v.getSourceMap(mathOp), left, right, mathOp.GetText())
|
||||
}
|
||||
|
||||
seq := ctx.ExpressionSequence()
|
||||
|
||||
if seq != nil {
|
||||
// seq := seq.(*fql.ExpressionSequenceContext)
|
||||
|
||||
return nil, core.Error(ErrNotImplemented, "expression sequence")
|
||||
}
|
||||
|
||||
// TODO: Complete it
|
||||
return nil, ErrNotImplemented
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitAllExpressions(contexts []fql.IExpressionContext, scope *scope) ([]core.Expression, error) {
|
||||
ret := make([]core.Expression, 0, len(contexts))
|
||||
|
||||
for _, ctx := range contexts {
|
||||
exp, err := v.doVisitExpression(ctx.(*fql.ExpressionContext), scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret = append(ret, exp)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitFunctionCallExpression(context *fql.FunctionCallExpressionContext, scope *scope) (collections.IterableExpression, error) {
|
||||
args := make([]core.Expression, 0, 5)
|
||||
argsCtx := context.Arguments()
|
||||
|
||||
if argsCtx != nil {
|
||||
argsCtx := argsCtx.(*fql.ArgumentsContext)
|
||||
|
||||
for _, arg := range argsCtx.AllExpression() {
|
||||
exp, err := v.doVisitExpression(arg.(*fql.ExpressionContext), scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
args = append(args, exp)
|
||||
}
|
||||
}
|
||||
|
||||
funcName := context.Identifier().GetText()
|
||||
|
||||
fun, exists := v.funcs[funcName]
|
||||
|
||||
if !exists {
|
||||
return nil, core.Error(core.ErrNotFound, fmt.Sprintf("function: '%s'", funcName))
|
||||
}
|
||||
|
||||
return expressions.NewFunctionCallExpression(
|
||||
v.getSourceMap(context),
|
||||
fun,
|
||||
args...,
|
||||
)
|
||||
}
|
||||
|
||||
func (v *visitor) visitAll(nodes []antlr.Tree, scope *scope) ([]core.Expression, error) {
|
||||
if nodes == nil {
|
||||
return make([]core.Expression, 0, 0), nil
|
||||
}
|
||||
|
||||
res := make([]core.Expression, 0, len(nodes))
|
||||
|
||||
for idx, node := range nodes {
|
||||
out, err := v.visit(node, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res[idx] = out
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (v *visitor) visit(node antlr.Tree, scope *scope) (core.Expression, error) {
|
||||
var out core.Expression
|
||||
var err error
|
||||
|
||||
switch node.(type) {
|
||||
case *fql.BodyContext:
|
||||
out, err = v.doVisitBody(node.(*fql.BodyContext), scope)
|
||||
case *fql.ExpressionContext:
|
||||
out, err = v.doVisitExpression(node.(*fql.ExpressionContext), scope)
|
||||
case *fql.ForExpressionContext:
|
||||
out, err = v.doVisitForExpression(node.(*fql.ForExpressionContext), scope)
|
||||
case *fql.ReturnExpressionContext:
|
||||
out, err = v.doVisitReturnExpression(node.(*fql.ReturnExpressionContext), scope)
|
||||
case *fql.ArrayLiteralContext:
|
||||
out, err = v.doVisitArrayLiteral(node.(*fql.ArrayLiteralContext), scope)
|
||||
case *fql.ObjectLiteralContext:
|
||||
out, err = v.doVisitObjectLiteral(node.(*fql.ObjectLiteralContext), scope)
|
||||
case *fql.StringLiteralContext:
|
||||
out, err = v.doVisitStringLiteral(node.(*fql.StringLiteralContext))
|
||||
case *fql.IntegerLiteralContext:
|
||||
out, err = v.doVisitIntegerLiteral(node.(*fql.IntegerLiteralContext))
|
||||
case *fql.FloatLiteralContext:
|
||||
out, err = v.doVisitFloatLiteral(node.(*fql.FloatLiteralContext))
|
||||
case *fql.BooleanLiteralContext:
|
||||
out, err = v.doVisitBooleanLiteral(node.(*fql.BooleanLiteralContext))
|
||||
case *fql.NoneLiteralContext:
|
||||
out, err = v.doVisitNoneLiteral(node.(*fql.NoneLiteralContext))
|
||||
case *fql.VariableContext:
|
||||
out, err = v.doVisitVariable(node.(*fql.VariableContext), scope)
|
||||
case *fql.VariableDeclarationContext:
|
||||
out, err = v.doVisitVariableDeclaration(node.(*fql.VariableDeclarationContext), scope)
|
||||
case *fql.FunctionCallExpressionContext:
|
||||
out, err = v.doVisitFunctionCallExpression(node.(*fql.FunctionCallExpressionContext), scope)
|
||||
default:
|
||||
err = v.unexpectedToken(node)
|
||||
}
|
||||
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (v *visitor) unexpectedToken(node antlr.Tree) error {
|
||||
name := "undefined"
|
||||
ctx, ok := node.(antlr.RuleContext)
|
||||
|
||||
if ok {
|
||||
name = ctx.GetText()
|
||||
}
|
||||
|
||||
return errors.Errorf("unexpected token: %s", name)
|
||||
}
|
||||
|
||||
func (v *visitor) getSourceMap(rule antlr.ParserRuleContext) core.SourceMap {
|
||||
start := rule.GetStart()
|
||||
|
||||
return core.NewSourceMap(
|
||||
rule.GetText(),
|
||||
start.GetLine(),
|
||||
start.GetColumn(),
|
||||
)
|
||||
}
|
107
pkg/parser/antlr/FqlLexer.g4
Normal file
107
pkg/parser/antlr/FqlLexer.g4
Normal file
@@ -0,0 +1,107 @@
|
||||
lexer grammar FqlLexer;
|
||||
|
||||
// Skip
|
||||
MultiLineComment: '/*' .*? '*/' -> channel(HIDDEN);
|
||||
SingleLineComment: '//' ~[\r\n\u2028\u2029]* -> channel(HIDDEN);
|
||||
WhiteSpaces: [\t\u000B\u000C\u0020\u00A0]+ -> channel(HIDDEN);
|
||||
LineTerminator: [\r\n\u2028\u2029] -> channel(HIDDEN);
|
||||
|
||||
// Punctuation
|
||||
Colon: ':';
|
||||
SemiColon: ';';
|
||||
Comma: ',';
|
||||
Dot: '.';
|
||||
Ellipsis: '...';
|
||||
OpenBracket: '[';
|
||||
CloseBracket: ']';
|
||||
OpenParen: '(';
|
||||
CloseParen: ')';
|
||||
OpenBrace: '{';
|
||||
CloseBrace: '}';
|
||||
|
||||
// Comparison operators
|
||||
Gt: '>';
|
||||
Lt: '<';
|
||||
Eq: '==';
|
||||
Gte: '>=';
|
||||
Lte: '<=';
|
||||
Neq: '!=';
|
||||
|
||||
// Arithmetic operators
|
||||
Plus: '+';
|
||||
Minus: '-';
|
||||
MinusMinus: '--';
|
||||
PlusPlus: '++';
|
||||
Multi: '*';
|
||||
Div: '/';
|
||||
Mod: '%';
|
||||
|
||||
// Logical operators
|
||||
And: 'AND' | '&&';
|
||||
Or: 'OR' | '||';
|
||||
|
||||
// Other operators
|
||||
Assign: '=';
|
||||
Range: '..';
|
||||
QuestionMark: '?';
|
||||
RegexNotMatch: '!~';
|
||||
RegexMatch: '=~';
|
||||
|
||||
// Keywords
|
||||
// Common Keywords
|
||||
For: 'FOR';
|
||||
Return: 'RETURN';
|
||||
Distinct: 'DISTINCT';
|
||||
Filter: 'FILTER';
|
||||
Sort: 'SORT';
|
||||
Limit: 'LIMIT';
|
||||
Let: 'LET';
|
||||
Collect: 'COLLECT';
|
||||
SortDirection: 'ASC' | 'DESC';
|
||||
None: 'NONE';
|
||||
Null: 'NULL';
|
||||
BooleanLiteral: 'TRUE' | 'true' | 'FALSE' | 'false';
|
||||
|
||||
// Group operators
|
||||
Into: 'INTO';
|
||||
Keep: 'KEEP';
|
||||
With: 'WITH';
|
||||
Count: 'COUNT';
|
||||
All: 'ALL';
|
||||
Any: 'ANY';
|
||||
Aggregate: 'AGGREGATE';
|
||||
|
||||
// Unary operators
|
||||
Like: 'LIKE';
|
||||
Not: 'NOT' | '!';
|
||||
In: 'IN';
|
||||
|
||||
// Literals
|
||||
Identifier: Letter+ (Digit)*;
|
||||
StringLiteral: SQString | DQSring;
|
||||
IntegerLiteral: [0-9]+;
|
||||
FloatLiteral
|
||||
: DecimalIntegerLiteral '.' [0-9]* ExponentPart?
|
||||
| '.' [0-9]+ ExponentPart?
|
||||
| DecimalIntegerLiteral ExponentPart?
|
||||
;
|
||||
|
||||
// Fragments
|
||||
fragment HexDigit
|
||||
: [0-9a-fA-F]
|
||||
;
|
||||
fragment DecimalIntegerLiteral
|
||||
: '0'
|
||||
| [1-9] [0-9]*
|
||||
;
|
||||
fragment ExponentPart
|
||||
: [eE] [+-]? [0-9]+
|
||||
;
|
||||
fragment Letter
|
||||
: 'A'..'Z' | 'a'..'z'
|
||||
;
|
||||
fragment Digit
|
||||
: '0'..'9'
|
||||
;
|
||||
fragment DQSring: '"' ( '\\'. | '""' | ~('"'| '\\') )* '"';
|
||||
fragment SQString: '\'' ('\\'. | '\'\'' | ~('\'' | '\\'))* '\'';
|
109
pkg/parser/antlr/FqlLexer.tokens
Normal file
109
pkg/parser/antlr/FqlLexer.tokens
Normal file
@@ -0,0 +1,109 @@
|
||||
MultiLineComment=1
|
||||
SingleLineComment=2
|
||||
WhiteSpaces=3
|
||||
LineTerminator=4
|
||||
Colon=5
|
||||
SemiColon=6
|
||||
Comma=7
|
||||
Dot=8
|
||||
Ellipsis=9
|
||||
OpenBracket=10
|
||||
CloseBracket=11
|
||||
OpenParen=12
|
||||
CloseParen=13
|
||||
OpenBrace=14
|
||||
CloseBrace=15
|
||||
Gt=16
|
||||
Lt=17
|
||||
Eq=18
|
||||
Gte=19
|
||||
Lte=20
|
||||
Neq=21
|
||||
Plus=22
|
||||
Minus=23
|
||||
MinusMinus=24
|
||||
PlusPlus=25
|
||||
Multi=26
|
||||
Div=27
|
||||
Mod=28
|
||||
And=29
|
||||
Or=30
|
||||
Assign=31
|
||||
Range=32
|
||||
QuestionMark=33
|
||||
RegexNotMatch=34
|
||||
RegexMatch=35
|
||||
For=36
|
||||
Return=37
|
||||
Distinct=38
|
||||
Filter=39
|
||||
Sort=40
|
||||
Limit=41
|
||||
Let=42
|
||||
Collect=43
|
||||
SortDirection=44
|
||||
None=45
|
||||
Null=46
|
||||
BooleanLiteral=47
|
||||
Into=48
|
||||
Keep=49
|
||||
With=50
|
||||
Count=51
|
||||
All=52
|
||||
Any=53
|
||||
Aggregate=54
|
||||
Like=55
|
||||
Not=56
|
||||
In=57
|
||||
Identifier=58
|
||||
StringLiteral=59
|
||||
IntegerLiteral=60
|
||||
FloatLiteral=61
|
||||
':'=5
|
||||
';'=6
|
||||
','=7
|
||||
'.'=8
|
||||
'...'=9
|
||||
'['=10
|
||||
']'=11
|
||||
'('=12
|
||||
')'=13
|
||||
'{'=14
|
||||
'}'=15
|
||||
'>'=16
|
||||
'<'=17
|
||||
'=='=18
|
||||
'>='=19
|
||||
'<='=20
|
||||
'!='=21
|
||||
'+'=22
|
||||
'-'=23
|
||||
'--'=24
|
||||
'++'=25
|
||||
'*'=26
|
||||
'/'=27
|
||||
'%'=28
|
||||
'='=31
|
||||
'..'=32
|
||||
'?'=33
|
||||
'!~'=34
|
||||
'=~'=35
|
||||
'FOR'=36
|
||||
'RETURN'=37
|
||||
'DISTINCT'=38
|
||||
'FILTER'=39
|
||||
'SORT'=40
|
||||
'LIMIT'=41
|
||||
'LET'=42
|
||||
'COLLECT'=43
|
||||
'NONE'=45
|
||||
'NULL'=46
|
||||
'INTO'=48
|
||||
'KEEP'=49
|
||||
'WITH'=50
|
||||
'COUNT'=51
|
||||
'ALL'=52
|
||||
'ANY'=53
|
||||
'AGGREGATE'=54
|
||||
'LIKE'=55
|
||||
'IN'=57
|
278
pkg/parser/antlr/FqlParser.g4
Normal file
278
pkg/parser/antlr/FqlParser.g4
Normal file
@@ -0,0 +1,278 @@
|
||||
parser grammar FqlParser;
|
||||
|
||||
options { tokenVocab=FqlLexer; }
|
||||
|
||||
program
|
||||
: body
|
||||
;
|
||||
|
||||
body
|
||||
: (bodyStatement)* bodyExpression
|
||||
;
|
||||
|
||||
bodyStatement
|
||||
: functionCallExpression
|
||||
| variableDeclaration
|
||||
;
|
||||
|
||||
bodyExpression
|
||||
: returnExpression
|
||||
| forExpression
|
||||
;
|
||||
|
||||
returnExpression
|
||||
: Return (Distinct)? expression
|
||||
| Return (Distinct)? OpenParen forExpression CloseParen
|
||||
;
|
||||
|
||||
forExpression
|
||||
: For forExpressionValueVariable (Comma forExpressionKeyVariable)? In forExpressionSource
|
||||
(forExpressionClause)*
|
||||
(forExpressionBody)*
|
||||
forExpressionReturn
|
||||
;
|
||||
|
||||
forExpressionValueVariable
|
||||
: Identifier
|
||||
;
|
||||
|
||||
forExpressionKeyVariable
|
||||
: Identifier
|
||||
;
|
||||
|
||||
forExpressionSource
|
||||
: functionCallExpression
|
||||
| arrayLiteral
|
||||
| objectLiteral
|
||||
| variable
|
||||
| memberExpression
|
||||
;
|
||||
|
||||
forExpressionClause
|
||||
: limitClause
|
||||
| sortClause
|
||||
| filterClause
|
||||
| collectClause
|
||||
;
|
||||
|
||||
filterClause
|
||||
: Filter expression
|
||||
;
|
||||
|
||||
limitClause
|
||||
: Limit IntegerLiteral (Comma IntegerLiteral)?
|
||||
;
|
||||
|
||||
sortClause
|
||||
: Sort sortClauseExpression (',' sortClauseExpression)*
|
||||
;
|
||||
|
||||
sortClauseExpression
|
||||
: expression SortDirection?
|
||||
;
|
||||
|
||||
collectClause
|
||||
: Collect collectVariable Assign expression
|
||||
| Collect collectVariable Assign expression Into collectGroupVariable
|
||||
| Collect collectVariable Assign expression Into collectGroupVariable Keep collectKeepVariable
|
||||
| Collect collectVariable Assign expression With Count collectCountVariable
|
||||
| Collect collectVariable Assign expression Aggregate collectAggregateVariable Assign collectAggregateExpression
|
||||
| Collect Aggregate collectAggregateVariable Assign collectAggregateExpression
|
||||
| Collect With Count Into collectCountVariable
|
||||
;
|
||||
|
||||
collectVariable
|
||||
: Identifier
|
||||
;
|
||||
|
||||
collectGroupVariable
|
||||
: Identifier
|
||||
;
|
||||
|
||||
collectKeepVariable
|
||||
: Identifier
|
||||
;
|
||||
|
||||
collectCountVariable
|
||||
: Identifier
|
||||
;
|
||||
|
||||
collectAggregateVariable
|
||||
: Identifier
|
||||
;
|
||||
|
||||
collectAggregateExpression
|
||||
: expression
|
||||
;
|
||||
|
||||
collectOption
|
||||
:
|
||||
;
|
||||
|
||||
forExpressionBody
|
||||
: variableDeclaration
|
||||
| functionCallExpression
|
||||
;
|
||||
|
||||
forExpressionReturn
|
||||
: returnExpression
|
||||
| forExpression
|
||||
;
|
||||
|
||||
|
||||
variableDeclaration
|
||||
: Let Identifier Assign expression
|
||||
| Let Identifier Assign OpenParen forExpression CloseParen
|
||||
;
|
||||
|
||||
variable
|
||||
: Identifier
|
||||
;
|
||||
|
||||
arrayLiteral
|
||||
: OpenBracket arrayElementList? CloseBracket
|
||||
;
|
||||
|
||||
objectLiteral
|
||||
: OpenBrace (propertyAssignment (Comma propertyAssignment)*)? Comma? CloseBrace
|
||||
;
|
||||
|
||||
booleanLiteral
|
||||
: BooleanLiteral
|
||||
;
|
||||
|
||||
stringLiteral
|
||||
: StringLiteral
|
||||
;
|
||||
|
||||
integerLiteral
|
||||
: IntegerLiteral
|
||||
;
|
||||
|
||||
floatLiteral
|
||||
: FloatLiteral
|
||||
;
|
||||
|
||||
noneLiteral
|
||||
: Null
|
||||
| None
|
||||
;
|
||||
|
||||
arrayElementList
|
||||
: expression (Comma + expression)*
|
||||
;
|
||||
|
||||
propertyAssignment
|
||||
: propertyName Colon expression
|
||||
| computedPropertyName Colon expression
|
||||
| shorthandPropertyName
|
||||
;
|
||||
|
||||
memberExpression
|
||||
: Identifier (Dot propertyName (computedPropertyName)*)+
|
||||
| Identifier computedPropertyName (Dot propertyName (computedPropertyName)*)* (computedPropertyName (Dot propertyName)*)*
|
||||
;
|
||||
|
||||
shorthandPropertyName
|
||||
: variable
|
||||
;
|
||||
|
||||
computedPropertyName
|
||||
: OpenBracket expression CloseBracket
|
||||
;
|
||||
|
||||
propertyName
|
||||
: Identifier
|
||||
;
|
||||
|
||||
expressionSequence
|
||||
: expression (Comma expression)*
|
||||
;
|
||||
|
||||
functionCallExpression
|
||||
: Identifier arguments
|
||||
;
|
||||
|
||||
arguments
|
||||
: OpenParen(expression (Comma expression)*)?CloseParen
|
||||
;
|
||||
|
||||
//ternaryExpression
|
||||
// : expression QuestionMark expression Colon expression
|
||||
// ;
|
||||
|
||||
expression
|
||||
: expression equalityOperator expression
|
||||
| expression logicalOperator expression
|
||||
| expression mathOperator expression
|
||||
| functionCallExpression
|
||||
| OpenParen expressionSequence CloseParen
|
||||
| expression PlusPlus
|
||||
| expression MinusMinus
|
||||
| PlusPlus expression
|
||||
| MinusMinus expression
|
||||
| Plus expression
|
||||
| Minus expression
|
||||
| Not expression
|
||||
| expression QuestionMark expression Colon expression
|
||||
| stringLiteral
|
||||
| integerLiteral
|
||||
| floatLiteral
|
||||
| booleanLiteral
|
||||
| arrayLiteral
|
||||
| objectLiteral
|
||||
| variable
|
||||
| memberExpression
|
||||
| noneLiteral
|
||||
;
|
||||
|
||||
reservedWord
|
||||
: keyword
|
||||
;
|
||||
|
||||
keyword
|
||||
: Return
|
||||
| In
|
||||
| Filter
|
||||
| Sort
|
||||
| SortDirection
|
||||
| Limit
|
||||
| Let
|
||||
| Collect
|
||||
| Distinct
|
||||
| BooleanLiteral
|
||||
| None
|
||||
| Null
|
||||
| All
|
||||
| Any
|
||||
| Aggregate
|
||||
;
|
||||
|
||||
equalityOperator
|
||||
: Gt
|
||||
| Lt
|
||||
| Eq
|
||||
| Gte
|
||||
| Lte
|
||||
| Neq
|
||||
;
|
||||
|
||||
logicalOperator
|
||||
: And
|
||||
| Or
|
||||
;
|
||||
|
||||
mathOperator
|
||||
: Plus
|
||||
| Minus
|
||||
| Multi
|
||||
| Div
|
||||
| Mod
|
||||
;
|
||||
|
||||
unaryOperator
|
||||
: Not
|
||||
| Plus
|
||||
| Minus
|
||||
| Like
|
||||
;
|
207
pkg/parser/fql/FqlLexer.interp
Normal file
207
pkg/parser/fql/FqlLexer.interp
Normal file
File diff suppressed because one or more lines are too long
109
pkg/parser/fql/FqlLexer.tokens
Normal file
109
pkg/parser/fql/FqlLexer.tokens
Normal file
@@ -0,0 +1,109 @@
|
||||
MultiLineComment=1
|
||||
SingleLineComment=2
|
||||
WhiteSpaces=3
|
||||
LineTerminator=4
|
||||
Colon=5
|
||||
SemiColon=6
|
||||
Comma=7
|
||||
Dot=8
|
||||
Ellipsis=9
|
||||
OpenBracket=10
|
||||
CloseBracket=11
|
||||
OpenParen=12
|
||||
CloseParen=13
|
||||
OpenBrace=14
|
||||
CloseBrace=15
|
||||
Gt=16
|
||||
Lt=17
|
||||
Eq=18
|
||||
Gte=19
|
||||
Lte=20
|
||||
Neq=21
|
||||
Plus=22
|
||||
Minus=23
|
||||
MinusMinus=24
|
||||
PlusPlus=25
|
||||
Multi=26
|
||||
Div=27
|
||||
Mod=28
|
||||
And=29
|
||||
Or=30
|
||||
Assign=31
|
||||
Range=32
|
||||
QuestionMark=33
|
||||
RegexNotMatch=34
|
||||
RegexMatch=35
|
||||
For=36
|
||||
Return=37
|
||||
Distinct=38
|
||||
Filter=39
|
||||
Sort=40
|
||||
Limit=41
|
||||
Let=42
|
||||
Collect=43
|
||||
SortDirection=44
|
||||
None=45
|
||||
Null=46
|
||||
BooleanLiteral=47
|
||||
Into=48
|
||||
Keep=49
|
||||
With=50
|
||||
Count=51
|
||||
All=52
|
||||
Any=53
|
||||
Aggregate=54
|
||||
Like=55
|
||||
Not=56
|
||||
In=57
|
||||
Identifier=58
|
||||
StringLiteral=59
|
||||
IntegerLiteral=60
|
||||
FloatLiteral=61
|
||||
':'=5
|
||||
';'=6
|
||||
','=7
|
||||
'.'=8
|
||||
'...'=9
|
||||
'['=10
|
||||
']'=11
|
||||
'('=12
|
||||
')'=13
|
||||
'{'=14
|
||||
'}'=15
|
||||
'>'=16
|
||||
'<'=17
|
||||
'=='=18
|
||||
'>='=19
|
||||
'<='=20
|
||||
'!='=21
|
||||
'+'=22
|
||||
'-'=23
|
||||
'--'=24
|
||||
'++'=25
|
||||
'*'=26
|
||||
'/'=27
|
||||
'%'=28
|
||||
'='=31
|
||||
'..'=32
|
||||
'?'=33
|
||||
'!~'=34
|
||||
'=~'=35
|
||||
'FOR'=36
|
||||
'RETURN'=37
|
||||
'DISTINCT'=38
|
||||
'FILTER'=39
|
||||
'SORT'=40
|
||||
'LIMIT'=41
|
||||
'LET'=42
|
||||
'COLLECT'=43
|
||||
'NONE'=45
|
||||
'NULL'=46
|
||||
'INTO'=48
|
||||
'KEEP'=49
|
||||
'WITH'=50
|
||||
'COUNT'=51
|
||||
'ALL'=52
|
||||
'ANY'=53
|
||||
'AGGREGATE'=54
|
||||
'LIKE'=55
|
||||
'IN'=57
|
182
pkg/parser/fql/FqlParser.interp
Normal file
182
pkg/parser/fql/FqlParser.interp
Normal file
File diff suppressed because one or more lines are too long
109
pkg/parser/fql/FqlParser.tokens
Normal file
109
pkg/parser/fql/FqlParser.tokens
Normal file
@@ -0,0 +1,109 @@
|
||||
MultiLineComment=1
|
||||
SingleLineComment=2
|
||||
WhiteSpaces=3
|
||||
LineTerminator=4
|
||||
Colon=5
|
||||
SemiColon=6
|
||||
Comma=7
|
||||
Dot=8
|
||||
Ellipsis=9
|
||||
OpenBracket=10
|
||||
CloseBracket=11
|
||||
OpenParen=12
|
||||
CloseParen=13
|
||||
OpenBrace=14
|
||||
CloseBrace=15
|
||||
Gt=16
|
||||
Lt=17
|
||||
Eq=18
|
||||
Gte=19
|
||||
Lte=20
|
||||
Neq=21
|
||||
Plus=22
|
||||
Minus=23
|
||||
MinusMinus=24
|
||||
PlusPlus=25
|
||||
Multi=26
|
||||
Div=27
|
||||
Mod=28
|
||||
And=29
|
||||
Or=30
|
||||
Assign=31
|
||||
Range=32
|
||||
QuestionMark=33
|
||||
RegexNotMatch=34
|
||||
RegexMatch=35
|
||||
For=36
|
||||
Return=37
|
||||
Distinct=38
|
||||
Filter=39
|
||||
Sort=40
|
||||
Limit=41
|
||||
Let=42
|
||||
Collect=43
|
||||
SortDirection=44
|
||||
None=45
|
||||
Null=46
|
||||
BooleanLiteral=47
|
||||
Into=48
|
||||
Keep=49
|
||||
With=50
|
||||
Count=51
|
||||
All=52
|
||||
Any=53
|
||||
Aggregate=54
|
||||
Like=55
|
||||
Not=56
|
||||
In=57
|
||||
Identifier=58
|
||||
StringLiteral=59
|
||||
IntegerLiteral=60
|
||||
FloatLiteral=61
|
||||
':'=5
|
||||
';'=6
|
||||
','=7
|
||||
'.'=8
|
||||
'...'=9
|
||||
'['=10
|
||||
']'=11
|
||||
'('=12
|
||||
')'=13
|
||||
'{'=14
|
||||
'}'=15
|
||||
'>'=16
|
||||
'<'=17
|
||||
'=='=18
|
||||
'>='=19
|
||||
'<='=20
|
||||
'!='=21
|
||||
'+'=22
|
||||
'-'=23
|
||||
'--'=24
|
||||
'++'=25
|
||||
'*'=26
|
||||
'/'=27
|
||||
'%'=28
|
||||
'='=31
|
||||
'..'=32
|
||||
'?'=33
|
||||
'!~'=34
|
||||
'=~'=35
|
||||
'FOR'=36
|
||||
'RETURN'=37
|
||||
'DISTINCT'=38
|
||||
'FILTER'=39
|
||||
'SORT'=40
|
||||
'LIMIT'=41
|
||||
'LET'=42
|
||||
'COLLECT'=43
|
||||
'NONE'=45
|
||||
'NULL'=46
|
||||
'INTO'=48
|
||||
'KEEP'=49
|
||||
'WITH'=50
|
||||
'COUNT'=51
|
||||
'ALL'=52
|
||||
'ANY'=53
|
||||
'AGGREGATE'=54
|
||||
'LIKE'=55
|
||||
'IN'=57
|
382
pkg/parser/fql/fql_lexer.go
Normal file
382
pkg/parser/fql/fql_lexer.go
Normal file
@@ -0,0 +1,382 @@
|
||||
// Code generated from antlr/FqlLexer.g4 by ANTLR 4.7.1. DO NOT EDIT.
|
||||
|
||||
package fql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unicode"
|
||||
|
||||
"github.com/antlr/antlr4/runtime/Go/antlr"
|
||||
)
|
||||
|
||||
// Suppress unused import error
|
||||
var _ = fmt.Printf
|
||||
var _ = unicode.IsLetter
|
||||
|
||||
var serializedLexerAtn = []uint16{
|
||||
3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 2, 63, 496,
|
||||
8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7,
|
||||
9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12,
|
||||
4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4,
|
||||
18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23,
|
||||
9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9,
|
||||
28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33,
|
||||
4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4,
|
||||
39, 9, 39, 4, 40, 9, 40, 4, 41, 9, 41, 4, 42, 9, 42, 4, 43, 9, 43, 4, 44,
|
||||
9, 44, 4, 45, 9, 45, 4, 46, 9, 46, 4, 47, 9, 47, 4, 48, 9, 48, 4, 49, 9,
|
||||
49, 4, 50, 9, 50, 4, 51, 9, 51, 4, 52, 9, 52, 4, 53, 9, 53, 4, 54, 9, 54,
|
||||
4, 55, 9, 55, 4, 56, 9, 56, 4, 57, 9, 57, 4, 58, 9, 58, 4, 59, 9, 59, 4,
|
||||
60, 9, 60, 4, 61, 9, 61, 4, 62, 9, 62, 4, 63, 9, 63, 4, 64, 9, 64, 4, 65,
|
||||
9, 65, 4, 66, 9, 66, 4, 67, 9, 67, 4, 68, 9, 68, 4, 69, 9, 69, 3, 2, 3,
|
||||
2, 3, 2, 3, 2, 7, 2, 144, 10, 2, 12, 2, 14, 2, 147, 11, 2, 3, 2, 3, 2,
|
||||
3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 158, 10, 3, 12, 3, 14,
|
||||
3, 161, 11, 3, 3, 3, 3, 3, 3, 4, 6, 4, 166, 10, 4, 13, 4, 14, 4, 167, 3,
|
||||
4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3,
|
||||
9, 3, 9, 3, 10, 3, 10, 3, 10, 3, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13,
|
||||
3, 13, 3, 14, 3, 14, 3, 15, 3, 15, 3, 16, 3, 16, 3, 17, 3, 17, 3, 18, 3,
|
||||
18, 3, 19, 3, 19, 3, 19, 3, 20, 3, 20, 3, 20, 3, 21, 3, 21, 3, 21, 3, 22,
|
||||
3, 22, 3, 22, 3, 23, 3, 23, 3, 24, 3, 24, 3, 25, 3, 25, 3, 25, 3, 26, 3,
|
||||
26, 3, 26, 3, 27, 3, 27, 3, 28, 3, 28, 3, 29, 3, 29, 3, 30, 3, 30, 3, 30,
|
||||
3, 30, 3, 30, 5, 30, 237, 10, 30, 3, 31, 3, 31, 3, 31, 3, 31, 5, 31, 243,
|
||||
10, 31, 3, 32, 3, 32, 3, 33, 3, 33, 3, 33, 3, 34, 3, 34, 3, 35, 3, 35,
|
||||
3, 35, 3, 36, 3, 36, 3, 36, 3, 37, 3, 37, 3, 37, 3, 37, 3, 38, 3, 38, 3,
|
||||
38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 39, 3, 39, 3, 39, 3, 39, 3, 39, 3, 39,
|
||||
3, 39, 3, 39, 3, 39, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40, 3,
|
||||
41, 3, 41, 3, 41, 3, 41, 3, 41, 3, 42, 3, 42, 3, 42, 3, 42, 3, 42, 3, 42,
|
||||
3, 43, 3, 43, 3, 43, 3, 43, 3, 44, 3, 44, 3, 44, 3, 44, 3, 44, 3, 44, 3,
|
||||
44, 3, 44, 3, 45, 3, 45, 3, 45, 3, 45, 3, 45, 3, 45, 3, 45, 5, 45, 315,
|
||||
10, 45, 3, 46, 3, 46, 3, 46, 3, 46, 3, 46, 3, 47, 3, 47, 3, 47, 3, 47,
|
||||
3, 47, 3, 48, 3, 48, 3, 48, 3, 48, 3, 48, 3, 48, 3, 48, 3, 48, 3, 48, 3,
|
||||
48, 3, 48, 3, 48, 3, 48, 3, 48, 3, 48, 3, 48, 3, 48, 3, 48, 5, 48, 345,
|
||||
10, 48, 3, 49, 3, 49, 3, 49, 3, 49, 3, 49, 3, 50, 3, 50, 3, 50, 3, 50,
|
||||
3, 50, 3, 51, 3, 51, 3, 51, 3, 51, 3, 51, 3, 52, 3, 52, 3, 52, 3, 52, 3,
|
||||
52, 3, 52, 3, 53, 3, 53, 3, 53, 3, 53, 3, 54, 3, 54, 3, 54, 3, 54, 3, 55,
|
||||
3, 55, 3, 55, 3, 55, 3, 55, 3, 55, 3, 55, 3, 55, 3, 55, 3, 55, 3, 56, 3,
|
||||
56, 3, 56, 3, 56, 3, 56, 3, 57, 3, 57, 3, 57, 3, 57, 5, 57, 395, 10, 57,
|
||||
3, 58, 3, 58, 3, 58, 3, 59, 6, 59, 401, 10, 59, 13, 59, 14, 59, 402, 3,
|
||||
59, 7, 59, 406, 10, 59, 12, 59, 14, 59, 409, 11, 59, 3, 60, 3, 60, 5, 60,
|
||||
413, 10, 60, 3, 61, 6, 61, 416, 10, 61, 13, 61, 14, 61, 417, 3, 62, 3,
|
||||
62, 3, 62, 7, 62, 423, 10, 62, 12, 62, 14, 62, 426, 11, 62, 3, 62, 5, 62,
|
||||
429, 10, 62, 3, 62, 3, 62, 6, 62, 433, 10, 62, 13, 62, 14, 62, 434, 3,
|
||||
62, 5, 62, 438, 10, 62, 3, 62, 3, 62, 5, 62, 442, 10, 62, 5, 62, 444, 10,
|
||||
62, 3, 63, 3, 63, 3, 64, 3, 64, 3, 64, 7, 64, 451, 10, 64, 12, 64, 14,
|
||||
64, 454, 11, 64, 5, 64, 456, 10, 64, 3, 65, 3, 65, 5, 65, 460, 10, 65,
|
||||
3, 65, 6, 65, 463, 10, 65, 13, 65, 14, 65, 464, 3, 66, 3, 66, 3, 67, 3,
|
||||
67, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 7, 68, 477, 10, 68, 12, 68,
|
||||
14, 68, 480, 11, 68, 3, 68, 3, 68, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3,
|
||||
69, 7, 69, 490, 10, 69, 12, 69, 14, 69, 493, 11, 69, 3, 69, 3, 69, 3, 145,
|
||||
2, 70, 3, 3, 5, 4, 7, 5, 9, 6, 11, 7, 13, 8, 15, 9, 17, 10, 19, 11, 21,
|
||||
12, 23, 13, 25, 14, 27, 15, 29, 16, 31, 17, 33, 18, 35, 19, 37, 20, 39,
|
||||
21, 41, 22, 43, 23, 45, 24, 47, 25, 49, 26, 51, 27, 53, 28, 55, 29, 57,
|
||||
30, 59, 31, 61, 32, 63, 33, 65, 34, 67, 35, 69, 36, 71, 37, 73, 38, 75,
|
||||
39, 77, 40, 79, 41, 81, 42, 83, 43, 85, 44, 87, 45, 89, 46, 91, 47, 93,
|
||||
48, 95, 49, 97, 50, 99, 51, 101, 52, 103, 53, 105, 54, 107, 55, 109, 56,
|
||||
111, 57, 113, 58, 115, 59, 117, 60, 119, 61, 121, 62, 123, 63, 125, 2,
|
||||
127, 2, 129, 2, 131, 2, 133, 2, 135, 2, 137, 2, 3, 2, 12, 5, 2, 12, 12,
|
||||
15, 15, 8234, 8235, 6, 2, 11, 11, 13, 14, 34, 34, 162, 162, 3, 2, 50, 59,
|
||||
5, 2, 50, 59, 67, 72, 99, 104, 3, 2, 51, 59, 4, 2, 71, 71, 103, 103, 4,
|
||||
2, 45, 45, 47, 47, 4, 2, 67, 92, 99, 124, 4, 2, 36, 36, 94, 94, 4, 2, 41,
|
||||
41, 94, 94, 2, 519, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2,
|
||||
2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2,
|
||||
2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 23, 3,
|
||||
2, 2, 2, 2, 25, 3, 2, 2, 2, 2, 27, 3, 2, 2, 2, 2, 29, 3, 2, 2, 2, 2, 31,
|
||||
3, 2, 2, 2, 2, 33, 3, 2, 2, 2, 2, 35, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 2,
|
||||
39, 3, 2, 2, 2, 2, 41, 3, 2, 2, 2, 2, 43, 3, 2, 2, 2, 2, 45, 3, 2, 2, 2,
|
||||
2, 47, 3, 2, 2, 2, 2, 49, 3, 2, 2, 2, 2, 51, 3, 2, 2, 2, 2, 53, 3, 2, 2,
|
||||
2, 2, 55, 3, 2, 2, 2, 2, 57, 3, 2, 2, 2, 2, 59, 3, 2, 2, 2, 2, 61, 3, 2,
|
||||
2, 2, 2, 63, 3, 2, 2, 2, 2, 65, 3, 2, 2, 2, 2, 67, 3, 2, 2, 2, 2, 69, 3,
|
||||
2, 2, 2, 2, 71, 3, 2, 2, 2, 2, 73, 3, 2, 2, 2, 2, 75, 3, 2, 2, 2, 2, 77,
|
||||
3, 2, 2, 2, 2, 79, 3, 2, 2, 2, 2, 81, 3, 2, 2, 2, 2, 83, 3, 2, 2, 2, 2,
|
||||
85, 3, 2, 2, 2, 2, 87, 3, 2, 2, 2, 2, 89, 3, 2, 2, 2, 2, 91, 3, 2, 2, 2,
|
||||
2, 93, 3, 2, 2, 2, 2, 95, 3, 2, 2, 2, 2, 97, 3, 2, 2, 2, 2, 99, 3, 2, 2,
|
||||
2, 2, 101, 3, 2, 2, 2, 2, 103, 3, 2, 2, 2, 2, 105, 3, 2, 2, 2, 2, 107,
|
||||
3, 2, 2, 2, 2, 109, 3, 2, 2, 2, 2, 111, 3, 2, 2, 2, 2, 113, 3, 2, 2, 2,
|
||||
2, 115, 3, 2, 2, 2, 2, 117, 3, 2, 2, 2, 2, 119, 3, 2, 2, 2, 2, 121, 3,
|
||||
2, 2, 2, 2, 123, 3, 2, 2, 2, 3, 139, 3, 2, 2, 2, 5, 153, 3, 2, 2, 2, 7,
|
||||
165, 3, 2, 2, 2, 9, 171, 3, 2, 2, 2, 11, 175, 3, 2, 2, 2, 13, 177, 3, 2,
|
||||
2, 2, 15, 179, 3, 2, 2, 2, 17, 181, 3, 2, 2, 2, 19, 183, 3, 2, 2, 2, 21,
|
||||
187, 3, 2, 2, 2, 23, 189, 3, 2, 2, 2, 25, 191, 3, 2, 2, 2, 27, 193, 3,
|
||||
2, 2, 2, 29, 195, 3, 2, 2, 2, 31, 197, 3, 2, 2, 2, 33, 199, 3, 2, 2, 2,
|
||||
35, 201, 3, 2, 2, 2, 37, 203, 3, 2, 2, 2, 39, 206, 3, 2, 2, 2, 41, 209,
|
||||
3, 2, 2, 2, 43, 212, 3, 2, 2, 2, 45, 215, 3, 2, 2, 2, 47, 217, 3, 2, 2,
|
||||
2, 49, 219, 3, 2, 2, 2, 51, 222, 3, 2, 2, 2, 53, 225, 3, 2, 2, 2, 55, 227,
|
||||
3, 2, 2, 2, 57, 229, 3, 2, 2, 2, 59, 236, 3, 2, 2, 2, 61, 242, 3, 2, 2,
|
||||
2, 63, 244, 3, 2, 2, 2, 65, 246, 3, 2, 2, 2, 67, 249, 3, 2, 2, 2, 69, 251,
|
||||
3, 2, 2, 2, 71, 254, 3, 2, 2, 2, 73, 257, 3, 2, 2, 2, 75, 261, 3, 2, 2,
|
||||
2, 77, 268, 3, 2, 2, 2, 79, 277, 3, 2, 2, 2, 81, 284, 3, 2, 2, 2, 83, 289,
|
||||
3, 2, 2, 2, 85, 295, 3, 2, 2, 2, 87, 299, 3, 2, 2, 2, 89, 314, 3, 2, 2,
|
||||
2, 91, 316, 3, 2, 2, 2, 93, 321, 3, 2, 2, 2, 95, 344, 3, 2, 2, 2, 97, 346,
|
||||
3, 2, 2, 2, 99, 351, 3, 2, 2, 2, 101, 356, 3, 2, 2, 2, 103, 361, 3, 2,
|
||||
2, 2, 105, 367, 3, 2, 2, 2, 107, 371, 3, 2, 2, 2, 109, 375, 3, 2, 2, 2,
|
||||
111, 385, 3, 2, 2, 2, 113, 394, 3, 2, 2, 2, 115, 396, 3, 2, 2, 2, 117,
|
||||
400, 3, 2, 2, 2, 119, 412, 3, 2, 2, 2, 121, 415, 3, 2, 2, 2, 123, 443,
|
||||
3, 2, 2, 2, 125, 445, 3, 2, 2, 2, 127, 455, 3, 2, 2, 2, 129, 457, 3, 2,
|
||||
2, 2, 131, 466, 3, 2, 2, 2, 133, 468, 3, 2, 2, 2, 135, 470, 3, 2, 2, 2,
|
||||
137, 483, 3, 2, 2, 2, 139, 140, 7, 49, 2, 2, 140, 141, 7, 44, 2, 2, 141,
|
||||
145, 3, 2, 2, 2, 142, 144, 11, 2, 2, 2, 143, 142, 3, 2, 2, 2, 144, 147,
|
||||
3, 2, 2, 2, 145, 146, 3, 2, 2, 2, 145, 143, 3, 2, 2, 2, 146, 148, 3, 2,
|
||||
2, 2, 147, 145, 3, 2, 2, 2, 148, 149, 7, 44, 2, 2, 149, 150, 7, 49, 2,
|
||||
2, 150, 151, 3, 2, 2, 2, 151, 152, 8, 2, 2, 2, 152, 4, 3, 2, 2, 2, 153,
|
||||
154, 7, 49, 2, 2, 154, 155, 7, 49, 2, 2, 155, 159, 3, 2, 2, 2, 156, 158,
|
||||
10, 2, 2, 2, 157, 156, 3, 2, 2, 2, 158, 161, 3, 2, 2, 2, 159, 157, 3, 2,
|
||||
2, 2, 159, 160, 3, 2, 2, 2, 160, 162, 3, 2, 2, 2, 161, 159, 3, 2, 2, 2,
|
||||
162, 163, 8, 3, 2, 2, 163, 6, 3, 2, 2, 2, 164, 166, 9, 3, 2, 2, 165, 164,
|
||||
3, 2, 2, 2, 166, 167, 3, 2, 2, 2, 167, 165, 3, 2, 2, 2, 167, 168, 3, 2,
|
||||
2, 2, 168, 169, 3, 2, 2, 2, 169, 170, 8, 4, 2, 2, 170, 8, 3, 2, 2, 2, 171,
|
||||
172, 9, 2, 2, 2, 172, 173, 3, 2, 2, 2, 173, 174, 8, 5, 2, 2, 174, 10, 3,
|
||||
2, 2, 2, 175, 176, 7, 60, 2, 2, 176, 12, 3, 2, 2, 2, 177, 178, 7, 61, 2,
|
||||
2, 178, 14, 3, 2, 2, 2, 179, 180, 7, 46, 2, 2, 180, 16, 3, 2, 2, 2, 181,
|
||||
182, 7, 48, 2, 2, 182, 18, 3, 2, 2, 2, 183, 184, 7, 48, 2, 2, 184, 185,
|
||||
7, 48, 2, 2, 185, 186, 7, 48, 2, 2, 186, 20, 3, 2, 2, 2, 187, 188, 7, 93,
|
||||
2, 2, 188, 22, 3, 2, 2, 2, 189, 190, 7, 95, 2, 2, 190, 24, 3, 2, 2, 2,
|
||||
191, 192, 7, 42, 2, 2, 192, 26, 3, 2, 2, 2, 193, 194, 7, 43, 2, 2, 194,
|
||||
28, 3, 2, 2, 2, 195, 196, 7, 125, 2, 2, 196, 30, 3, 2, 2, 2, 197, 198,
|
||||
7, 127, 2, 2, 198, 32, 3, 2, 2, 2, 199, 200, 7, 64, 2, 2, 200, 34, 3, 2,
|
||||
2, 2, 201, 202, 7, 62, 2, 2, 202, 36, 3, 2, 2, 2, 203, 204, 7, 63, 2, 2,
|
||||
204, 205, 7, 63, 2, 2, 205, 38, 3, 2, 2, 2, 206, 207, 7, 64, 2, 2, 207,
|
||||
208, 7, 63, 2, 2, 208, 40, 3, 2, 2, 2, 209, 210, 7, 62, 2, 2, 210, 211,
|
||||
7, 63, 2, 2, 211, 42, 3, 2, 2, 2, 212, 213, 7, 35, 2, 2, 213, 214, 7, 63,
|
||||
2, 2, 214, 44, 3, 2, 2, 2, 215, 216, 7, 45, 2, 2, 216, 46, 3, 2, 2, 2,
|
||||
217, 218, 7, 47, 2, 2, 218, 48, 3, 2, 2, 2, 219, 220, 7, 47, 2, 2, 220,
|
||||
221, 7, 47, 2, 2, 221, 50, 3, 2, 2, 2, 222, 223, 7, 45, 2, 2, 223, 224,
|
||||
7, 45, 2, 2, 224, 52, 3, 2, 2, 2, 225, 226, 7, 44, 2, 2, 226, 54, 3, 2,
|
||||
2, 2, 227, 228, 7, 49, 2, 2, 228, 56, 3, 2, 2, 2, 229, 230, 7, 39, 2, 2,
|
||||
230, 58, 3, 2, 2, 2, 231, 232, 7, 67, 2, 2, 232, 233, 7, 80, 2, 2, 233,
|
||||
237, 7, 70, 2, 2, 234, 235, 7, 40, 2, 2, 235, 237, 7, 40, 2, 2, 236, 231,
|
||||
3, 2, 2, 2, 236, 234, 3, 2, 2, 2, 237, 60, 3, 2, 2, 2, 238, 239, 7, 81,
|
||||
2, 2, 239, 243, 7, 84, 2, 2, 240, 241, 7, 126, 2, 2, 241, 243, 7, 126,
|
||||
2, 2, 242, 238, 3, 2, 2, 2, 242, 240, 3, 2, 2, 2, 243, 62, 3, 2, 2, 2,
|
||||
244, 245, 7, 63, 2, 2, 245, 64, 3, 2, 2, 2, 246, 247, 7, 48, 2, 2, 247,
|
||||
248, 7, 48, 2, 2, 248, 66, 3, 2, 2, 2, 249, 250, 7, 65, 2, 2, 250, 68,
|
||||
3, 2, 2, 2, 251, 252, 7, 35, 2, 2, 252, 253, 7, 128, 2, 2, 253, 70, 3,
|
||||
2, 2, 2, 254, 255, 7, 63, 2, 2, 255, 256, 7, 128, 2, 2, 256, 72, 3, 2,
|
||||
2, 2, 257, 258, 7, 72, 2, 2, 258, 259, 7, 81, 2, 2, 259, 260, 7, 84, 2,
|
||||
2, 260, 74, 3, 2, 2, 2, 261, 262, 7, 84, 2, 2, 262, 263, 7, 71, 2, 2, 263,
|
||||
264, 7, 86, 2, 2, 264, 265, 7, 87, 2, 2, 265, 266, 7, 84, 2, 2, 266, 267,
|
||||
7, 80, 2, 2, 267, 76, 3, 2, 2, 2, 268, 269, 7, 70, 2, 2, 269, 270, 7, 75,
|
||||
2, 2, 270, 271, 7, 85, 2, 2, 271, 272, 7, 86, 2, 2, 272, 273, 7, 75, 2,
|
||||
2, 273, 274, 7, 80, 2, 2, 274, 275, 7, 69, 2, 2, 275, 276, 7, 86, 2, 2,
|
||||
276, 78, 3, 2, 2, 2, 277, 278, 7, 72, 2, 2, 278, 279, 7, 75, 2, 2, 279,
|
||||
280, 7, 78, 2, 2, 280, 281, 7, 86, 2, 2, 281, 282, 7, 71, 2, 2, 282, 283,
|
||||
7, 84, 2, 2, 283, 80, 3, 2, 2, 2, 284, 285, 7, 85, 2, 2, 285, 286, 7, 81,
|
||||
2, 2, 286, 287, 7, 84, 2, 2, 287, 288, 7, 86, 2, 2, 288, 82, 3, 2, 2, 2,
|
||||
289, 290, 7, 78, 2, 2, 290, 291, 7, 75, 2, 2, 291, 292, 7, 79, 2, 2, 292,
|
||||
293, 7, 75, 2, 2, 293, 294, 7, 86, 2, 2, 294, 84, 3, 2, 2, 2, 295, 296,
|
||||
7, 78, 2, 2, 296, 297, 7, 71, 2, 2, 297, 298, 7, 86, 2, 2, 298, 86, 3,
|
||||
2, 2, 2, 299, 300, 7, 69, 2, 2, 300, 301, 7, 81, 2, 2, 301, 302, 7, 78,
|
||||
2, 2, 302, 303, 7, 78, 2, 2, 303, 304, 7, 71, 2, 2, 304, 305, 7, 69, 2,
|
||||
2, 305, 306, 7, 86, 2, 2, 306, 88, 3, 2, 2, 2, 307, 308, 7, 67, 2, 2, 308,
|
||||
309, 7, 85, 2, 2, 309, 315, 7, 69, 2, 2, 310, 311, 7, 70, 2, 2, 311, 312,
|
||||
7, 71, 2, 2, 312, 313, 7, 85, 2, 2, 313, 315, 7, 69, 2, 2, 314, 307, 3,
|
||||
2, 2, 2, 314, 310, 3, 2, 2, 2, 315, 90, 3, 2, 2, 2, 316, 317, 7, 80, 2,
|
||||
2, 317, 318, 7, 81, 2, 2, 318, 319, 7, 80, 2, 2, 319, 320, 7, 71, 2, 2,
|
||||
320, 92, 3, 2, 2, 2, 321, 322, 7, 80, 2, 2, 322, 323, 7, 87, 2, 2, 323,
|
||||
324, 7, 78, 2, 2, 324, 325, 7, 78, 2, 2, 325, 94, 3, 2, 2, 2, 326, 327,
|
||||
7, 86, 2, 2, 327, 328, 7, 84, 2, 2, 328, 329, 7, 87, 2, 2, 329, 345, 7,
|
||||
71, 2, 2, 330, 331, 7, 118, 2, 2, 331, 332, 7, 116, 2, 2, 332, 333, 7,
|
||||
119, 2, 2, 333, 345, 7, 103, 2, 2, 334, 335, 7, 72, 2, 2, 335, 336, 7,
|
||||
67, 2, 2, 336, 337, 7, 78, 2, 2, 337, 338, 7, 85, 2, 2, 338, 345, 7, 71,
|
||||
2, 2, 339, 340, 7, 104, 2, 2, 340, 341, 7, 99, 2, 2, 341, 342, 7, 110,
|
||||
2, 2, 342, 343, 7, 117, 2, 2, 343, 345, 7, 103, 2, 2, 344, 326, 3, 2, 2,
|
||||
2, 344, 330, 3, 2, 2, 2, 344, 334, 3, 2, 2, 2, 344, 339, 3, 2, 2, 2, 345,
|
||||
96, 3, 2, 2, 2, 346, 347, 7, 75, 2, 2, 347, 348, 7, 80, 2, 2, 348, 349,
|
||||
7, 86, 2, 2, 349, 350, 7, 81, 2, 2, 350, 98, 3, 2, 2, 2, 351, 352, 7, 77,
|
||||
2, 2, 352, 353, 7, 71, 2, 2, 353, 354, 7, 71, 2, 2, 354, 355, 7, 82, 2,
|
||||
2, 355, 100, 3, 2, 2, 2, 356, 357, 7, 89, 2, 2, 357, 358, 7, 75, 2, 2,
|
||||
358, 359, 7, 86, 2, 2, 359, 360, 7, 74, 2, 2, 360, 102, 3, 2, 2, 2, 361,
|
||||
362, 7, 69, 2, 2, 362, 363, 7, 81, 2, 2, 363, 364, 7, 87, 2, 2, 364, 365,
|
||||
7, 80, 2, 2, 365, 366, 7, 86, 2, 2, 366, 104, 3, 2, 2, 2, 367, 368, 7,
|
||||
67, 2, 2, 368, 369, 7, 78, 2, 2, 369, 370, 7, 78, 2, 2, 370, 106, 3, 2,
|
||||
2, 2, 371, 372, 7, 67, 2, 2, 372, 373, 7, 80, 2, 2, 373, 374, 7, 91, 2,
|
||||
2, 374, 108, 3, 2, 2, 2, 375, 376, 7, 67, 2, 2, 376, 377, 7, 73, 2, 2,
|
||||
377, 378, 7, 73, 2, 2, 378, 379, 7, 84, 2, 2, 379, 380, 7, 71, 2, 2, 380,
|
||||
381, 7, 73, 2, 2, 381, 382, 7, 67, 2, 2, 382, 383, 7, 86, 2, 2, 383, 384,
|
||||
7, 71, 2, 2, 384, 110, 3, 2, 2, 2, 385, 386, 7, 78, 2, 2, 386, 387, 7,
|
||||
75, 2, 2, 387, 388, 7, 77, 2, 2, 388, 389, 7, 71, 2, 2, 389, 112, 3, 2,
|
||||
2, 2, 390, 391, 7, 80, 2, 2, 391, 392, 7, 81, 2, 2, 392, 395, 7, 86, 2,
|
||||
2, 393, 395, 7, 35, 2, 2, 394, 390, 3, 2, 2, 2, 394, 393, 3, 2, 2, 2, 395,
|
||||
114, 3, 2, 2, 2, 396, 397, 7, 75, 2, 2, 397, 398, 7, 80, 2, 2, 398, 116,
|
||||
3, 2, 2, 2, 399, 401, 5, 131, 66, 2, 400, 399, 3, 2, 2, 2, 401, 402, 3,
|
||||
2, 2, 2, 402, 400, 3, 2, 2, 2, 402, 403, 3, 2, 2, 2, 403, 407, 3, 2, 2,
|
||||
2, 404, 406, 5, 133, 67, 2, 405, 404, 3, 2, 2, 2, 406, 409, 3, 2, 2, 2,
|
||||
407, 405, 3, 2, 2, 2, 407, 408, 3, 2, 2, 2, 408, 118, 3, 2, 2, 2, 409,
|
||||
407, 3, 2, 2, 2, 410, 413, 5, 137, 69, 2, 411, 413, 5, 135, 68, 2, 412,
|
||||
410, 3, 2, 2, 2, 412, 411, 3, 2, 2, 2, 413, 120, 3, 2, 2, 2, 414, 416,
|
||||
9, 4, 2, 2, 415, 414, 3, 2, 2, 2, 416, 417, 3, 2, 2, 2, 417, 415, 3, 2,
|
||||
2, 2, 417, 418, 3, 2, 2, 2, 418, 122, 3, 2, 2, 2, 419, 420, 5, 127, 64,
|
||||
2, 420, 424, 7, 48, 2, 2, 421, 423, 9, 4, 2, 2, 422, 421, 3, 2, 2, 2, 423,
|
||||
426, 3, 2, 2, 2, 424, 422, 3, 2, 2, 2, 424, 425, 3, 2, 2, 2, 425, 428,
|
||||
3, 2, 2, 2, 426, 424, 3, 2, 2, 2, 427, 429, 5, 129, 65, 2, 428, 427, 3,
|
||||
2, 2, 2, 428, 429, 3, 2, 2, 2, 429, 444, 3, 2, 2, 2, 430, 432, 7, 48, 2,
|
||||
2, 431, 433, 9, 4, 2, 2, 432, 431, 3, 2, 2, 2, 433, 434, 3, 2, 2, 2, 434,
|
||||
432, 3, 2, 2, 2, 434, 435, 3, 2, 2, 2, 435, 437, 3, 2, 2, 2, 436, 438,
|
||||
5, 129, 65, 2, 437, 436, 3, 2, 2, 2, 437, 438, 3, 2, 2, 2, 438, 444, 3,
|
||||
2, 2, 2, 439, 441, 5, 127, 64, 2, 440, 442, 5, 129, 65, 2, 441, 440, 3,
|
||||
2, 2, 2, 441, 442, 3, 2, 2, 2, 442, 444, 3, 2, 2, 2, 443, 419, 3, 2, 2,
|
||||
2, 443, 430, 3, 2, 2, 2, 443, 439, 3, 2, 2, 2, 444, 124, 3, 2, 2, 2, 445,
|
||||
446, 9, 5, 2, 2, 446, 126, 3, 2, 2, 2, 447, 456, 7, 50, 2, 2, 448, 452,
|
||||
9, 6, 2, 2, 449, 451, 9, 4, 2, 2, 450, 449, 3, 2, 2, 2, 451, 454, 3, 2,
|
||||
2, 2, 452, 450, 3, 2, 2, 2, 452, 453, 3, 2, 2, 2, 453, 456, 3, 2, 2, 2,
|
||||
454, 452, 3, 2, 2, 2, 455, 447, 3, 2, 2, 2, 455, 448, 3, 2, 2, 2, 456,
|
||||
128, 3, 2, 2, 2, 457, 459, 9, 7, 2, 2, 458, 460, 9, 8, 2, 2, 459, 458,
|
||||
3, 2, 2, 2, 459, 460, 3, 2, 2, 2, 460, 462, 3, 2, 2, 2, 461, 463, 9, 4,
|
||||
2, 2, 462, 461, 3, 2, 2, 2, 463, 464, 3, 2, 2, 2, 464, 462, 3, 2, 2, 2,
|
||||
464, 465, 3, 2, 2, 2, 465, 130, 3, 2, 2, 2, 466, 467, 9, 9, 2, 2, 467,
|
||||
132, 3, 2, 2, 2, 468, 469, 4, 50, 59, 2, 469, 134, 3, 2, 2, 2, 470, 478,
|
||||
7, 36, 2, 2, 471, 472, 7, 94, 2, 2, 472, 477, 11, 2, 2, 2, 473, 474, 7,
|
||||
36, 2, 2, 474, 477, 7, 36, 2, 2, 475, 477, 10, 10, 2, 2, 476, 471, 3, 2,
|
||||
2, 2, 476, 473, 3, 2, 2, 2, 476, 475, 3, 2, 2, 2, 477, 480, 3, 2, 2, 2,
|
||||
478, 476, 3, 2, 2, 2, 478, 479, 3, 2, 2, 2, 479, 481, 3, 2, 2, 2, 480,
|
||||
478, 3, 2, 2, 2, 481, 482, 7, 36, 2, 2, 482, 136, 3, 2, 2, 2, 483, 491,
|
||||
7, 41, 2, 2, 484, 485, 7, 94, 2, 2, 485, 490, 11, 2, 2, 2, 486, 487, 7,
|
||||
41, 2, 2, 487, 490, 7, 41, 2, 2, 488, 490, 10, 11, 2, 2, 489, 484, 3, 2,
|
||||
2, 2, 489, 486, 3, 2, 2, 2, 489, 488, 3, 2, 2, 2, 490, 493, 3, 2, 2, 2,
|
||||
491, 489, 3, 2, 2, 2, 491, 492, 3, 2, 2, 2, 492, 494, 3, 2, 2, 2, 493,
|
||||
491, 3, 2, 2, 2, 494, 495, 7, 41, 2, 2, 495, 138, 3, 2, 2, 2, 29, 2, 145,
|
||||
159, 167, 236, 242, 314, 344, 394, 402, 407, 412, 417, 424, 428, 434, 437,
|
||||
441, 443, 452, 455, 459, 464, 476, 478, 489, 491, 3, 2, 3, 2,
|
||||
}
|
||||
|
||||
var lexerDeserializer = antlr.NewATNDeserializer(nil)
|
||||
var lexerAtn = lexerDeserializer.DeserializeFromUInt16(serializedLexerAtn)
|
||||
|
||||
var lexerChannelNames = []string{
|
||||
"DEFAULT_TOKEN_CHANNEL", "HIDDEN",
|
||||
}
|
||||
|
||||
var lexerModeNames = []string{
|
||||
"DEFAULT_MODE",
|
||||
}
|
||||
|
||||
var lexerLiteralNames = []string{
|
||||
"", "", "", "", "", "':'", "';'", "','", "'.'", "'...'", "'['", "']'",
|
||||
"'('", "')'", "'{'", "'}'", "'>'", "'<'", "'=='", "'>='", "'<='", "'!='",
|
||||
"'+'", "'-'", "'--'", "'++'", "'*'", "'/'", "'%'", "", "", "'='", "'..'",
|
||||
"'?'", "'!~'", "'=~'", "'FOR'", "'RETURN'", "'DISTINCT'", "'FILTER'", "'SORT'",
|
||||
"'LIMIT'", "'LET'", "'COLLECT'", "", "'NONE'", "'NULL'", "", "'INTO'",
|
||||
"'KEEP'", "'WITH'", "'COUNT'", "'ALL'", "'ANY'", "'AGGREGATE'", "'LIKE'",
|
||||
"", "'IN'",
|
||||
}
|
||||
|
||||
var lexerSymbolicNames = []string{
|
||||
"", "MultiLineComment", "SingleLineComment", "WhiteSpaces", "LineTerminator",
|
||||
"Colon", "SemiColon", "Comma", "Dot", "Ellipsis", "OpenBracket", "CloseBracket",
|
||||
"OpenParen", "CloseParen", "OpenBrace", "CloseBrace", "Gt", "Lt", "Eq",
|
||||
"Gte", "Lte", "Neq", "Plus", "Minus", "MinusMinus", "PlusPlus", "Multi",
|
||||
"Div", "Mod", "And", "Or", "Assign", "Range", "QuestionMark", "RegexNotMatch",
|
||||
"RegexMatch", "For", "Return", "Distinct", "Filter", "Sort", "Limit", "Let",
|
||||
"Collect", "SortDirection", "None", "Null", "BooleanLiteral", "Into", "Keep",
|
||||
"With", "Count", "All", "Any", "Aggregate", "Like", "Not", "In", "Identifier",
|
||||
"StringLiteral", "IntegerLiteral", "FloatLiteral",
|
||||
}
|
||||
|
||||
var lexerRuleNames = []string{
|
||||
"MultiLineComment", "SingleLineComment", "WhiteSpaces", "LineTerminator",
|
||||
"Colon", "SemiColon", "Comma", "Dot", "Ellipsis", "OpenBracket", "CloseBracket",
|
||||
"OpenParen", "CloseParen", "OpenBrace", "CloseBrace", "Gt", "Lt", "Eq",
|
||||
"Gte", "Lte", "Neq", "Plus", "Minus", "MinusMinus", "PlusPlus", "Multi",
|
||||
"Div", "Mod", "And", "Or", "Assign", "Range", "QuestionMark", "RegexNotMatch",
|
||||
"RegexMatch", "For", "Return", "Distinct", "Filter", "Sort", "Limit", "Let",
|
||||
"Collect", "SortDirection", "None", "Null", "BooleanLiteral", "Into", "Keep",
|
||||
"With", "Count", "All", "Any", "Aggregate", "Like", "Not", "In", "Identifier",
|
||||
"StringLiteral", "IntegerLiteral", "FloatLiteral", "HexDigit", "DecimalIntegerLiteral",
|
||||
"ExponentPart", "Letter", "Digit", "DQSring", "SQString",
|
||||
}
|
||||
|
||||
type FqlLexer struct {
|
||||
*antlr.BaseLexer
|
||||
channelNames []string
|
||||
modeNames []string
|
||||
// TODO: EOF string
|
||||
}
|
||||
|
||||
var lexerDecisionToDFA = make([]*antlr.DFA, len(lexerAtn.DecisionToState))
|
||||
|
||||
func init() {
|
||||
for index, ds := range lexerAtn.DecisionToState {
|
||||
lexerDecisionToDFA[index] = antlr.NewDFA(ds, index)
|
||||
}
|
||||
}
|
||||
|
||||
func NewFqlLexer(input antlr.CharStream) *FqlLexer {
|
||||
|
||||
l := new(FqlLexer)
|
||||
|
||||
l.BaseLexer = antlr.NewBaseLexer(input)
|
||||
l.Interpreter = antlr.NewLexerATNSimulator(l, lexerAtn, lexerDecisionToDFA, antlr.NewPredictionContextCache())
|
||||
|
||||
l.channelNames = lexerChannelNames
|
||||
l.modeNames = lexerModeNames
|
||||
l.RuleNames = lexerRuleNames
|
||||
l.LiteralNames = lexerLiteralNames
|
||||
l.SymbolicNames = lexerSymbolicNames
|
||||
l.GrammarFileName = "FqlLexer.g4"
|
||||
// TODO: l.EOF = antlr.TokenEOF
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// FqlLexer tokens.
|
||||
const (
|
||||
FqlLexerMultiLineComment = 1
|
||||
FqlLexerSingleLineComment = 2
|
||||
FqlLexerWhiteSpaces = 3
|
||||
FqlLexerLineTerminator = 4
|
||||
FqlLexerColon = 5
|
||||
FqlLexerSemiColon = 6
|
||||
FqlLexerComma = 7
|
||||
FqlLexerDot = 8
|
||||
FqlLexerEllipsis = 9
|
||||
FqlLexerOpenBracket = 10
|
||||
FqlLexerCloseBracket = 11
|
||||
FqlLexerOpenParen = 12
|
||||
FqlLexerCloseParen = 13
|
||||
FqlLexerOpenBrace = 14
|
||||
FqlLexerCloseBrace = 15
|
||||
FqlLexerGt = 16
|
||||
FqlLexerLt = 17
|
||||
FqlLexerEq = 18
|
||||
FqlLexerGte = 19
|
||||
FqlLexerLte = 20
|
||||
FqlLexerNeq = 21
|
||||
FqlLexerPlus = 22
|
||||
FqlLexerMinus = 23
|
||||
FqlLexerMinusMinus = 24
|
||||
FqlLexerPlusPlus = 25
|
||||
FqlLexerMulti = 26
|
||||
FqlLexerDiv = 27
|
||||
FqlLexerMod = 28
|
||||
FqlLexerAnd = 29
|
||||
FqlLexerOr = 30
|
||||
FqlLexerAssign = 31
|
||||
FqlLexerRange = 32
|
||||
FqlLexerQuestionMark = 33
|
||||
FqlLexerRegexNotMatch = 34
|
||||
FqlLexerRegexMatch = 35
|
||||
FqlLexerFor = 36
|
||||
FqlLexerReturn = 37
|
||||
FqlLexerDistinct = 38
|
||||
FqlLexerFilter = 39
|
||||
FqlLexerSort = 40
|
||||
FqlLexerLimit = 41
|
||||
FqlLexerLet = 42
|
||||
FqlLexerCollect = 43
|
||||
FqlLexerSortDirection = 44
|
||||
FqlLexerNone = 45
|
||||
FqlLexerNull = 46
|
||||
FqlLexerBooleanLiteral = 47
|
||||
FqlLexerInto = 48
|
||||
FqlLexerKeep = 49
|
||||
FqlLexerWith = 50
|
||||
FqlLexerCount = 51
|
||||
FqlLexerAll = 52
|
||||
FqlLexerAny = 53
|
||||
FqlLexerAggregate = 54
|
||||
FqlLexerLike = 55
|
||||
FqlLexerNot = 56
|
||||
FqlLexerIn = 57
|
||||
FqlLexerIdentifier = 58
|
||||
FqlLexerStringLiteral = 59
|
||||
FqlLexerIntegerLiteral = 60
|
||||
FqlLexerFloatLiteral = 61
|
||||
)
|
7650
pkg/parser/fql/fql_parser.go
Normal file
7650
pkg/parser/fql/fql_parser.go
Normal file
File diff suppressed because it is too large
Load Diff
319
pkg/parser/fql/fqlparser_base_listener.go
Normal file
319
pkg/parser/fql/fqlparser_base_listener.go
Normal file
@@ -0,0 +1,319 @@
|
||||
// Code generated from antlr/FqlParser.g4 by ANTLR 4.7.1. DO NOT EDIT.
|
||||
|
||||
package fql // FqlParser
|
||||
import "github.com/antlr/antlr4/runtime/Go/antlr"
|
||||
|
||||
// BaseFqlParserListener is a complete listener for a parse tree produced by FqlParser.
|
||||
type BaseFqlParserListener struct{}
|
||||
|
||||
var _ FqlParserListener = &BaseFqlParserListener{}
|
||||
|
||||
// VisitTerminal is called when a terminal node is visited.
|
||||
func (s *BaseFqlParserListener) VisitTerminal(node antlr.TerminalNode) {}
|
||||
|
||||
// VisitErrorNode is called when an error node is visited.
|
||||
func (s *BaseFqlParserListener) VisitErrorNode(node antlr.ErrorNode) {}
|
||||
|
||||
// EnterEveryRule is called when any rule is entered.
|
||||
func (s *BaseFqlParserListener) EnterEveryRule(ctx antlr.ParserRuleContext) {}
|
||||
|
||||
// ExitEveryRule is called when any rule is exited.
|
||||
func (s *BaseFqlParserListener) ExitEveryRule(ctx antlr.ParserRuleContext) {}
|
||||
|
||||
// EnterProgram is called when production program is entered.
|
||||
func (s *BaseFqlParserListener) EnterProgram(ctx *ProgramContext) {}
|
||||
|
||||
// ExitProgram is called when production program is exited.
|
||||
func (s *BaseFqlParserListener) ExitProgram(ctx *ProgramContext) {}
|
||||
|
||||
// EnterBody is called when production body is entered.
|
||||
func (s *BaseFqlParserListener) EnterBody(ctx *BodyContext) {}
|
||||
|
||||
// ExitBody is called when production body is exited.
|
||||
func (s *BaseFqlParserListener) ExitBody(ctx *BodyContext) {}
|
||||
|
||||
// EnterBodyStatement is called when production bodyStatement is entered.
|
||||
func (s *BaseFqlParserListener) EnterBodyStatement(ctx *BodyStatementContext) {}
|
||||
|
||||
// ExitBodyStatement is called when production bodyStatement is exited.
|
||||
func (s *BaseFqlParserListener) ExitBodyStatement(ctx *BodyStatementContext) {}
|
||||
|
||||
// EnterBodyExpression is called when production bodyExpression is entered.
|
||||
func (s *BaseFqlParserListener) EnterBodyExpression(ctx *BodyExpressionContext) {}
|
||||
|
||||
// ExitBodyExpression is called when production bodyExpression is exited.
|
||||
func (s *BaseFqlParserListener) ExitBodyExpression(ctx *BodyExpressionContext) {}
|
||||
|
||||
// EnterReturnExpression is called when production returnExpression is entered.
|
||||
func (s *BaseFqlParserListener) EnterReturnExpression(ctx *ReturnExpressionContext) {}
|
||||
|
||||
// ExitReturnExpression is called when production returnExpression is exited.
|
||||
func (s *BaseFqlParserListener) ExitReturnExpression(ctx *ReturnExpressionContext) {}
|
||||
|
||||
// EnterForExpression is called when production forExpression is entered.
|
||||
func (s *BaseFqlParserListener) EnterForExpression(ctx *ForExpressionContext) {}
|
||||
|
||||
// ExitForExpression is called when production forExpression is exited.
|
||||
func (s *BaseFqlParserListener) ExitForExpression(ctx *ForExpressionContext) {}
|
||||
|
||||
// EnterForExpressionValueVariable is called when production forExpressionValueVariable is entered.
|
||||
func (s *BaseFqlParserListener) EnterForExpressionValueVariable(ctx *ForExpressionValueVariableContext) {
|
||||
}
|
||||
|
||||
// ExitForExpressionValueVariable is called when production forExpressionValueVariable is exited.
|
||||
func (s *BaseFqlParserListener) ExitForExpressionValueVariable(ctx *ForExpressionValueVariableContext) {
|
||||
}
|
||||
|
||||
// EnterForExpressionKeyVariable is called when production forExpressionKeyVariable is entered.
|
||||
func (s *BaseFqlParserListener) EnterForExpressionKeyVariable(ctx *ForExpressionKeyVariableContext) {}
|
||||
|
||||
// ExitForExpressionKeyVariable is called when production forExpressionKeyVariable is exited.
|
||||
func (s *BaseFqlParserListener) ExitForExpressionKeyVariable(ctx *ForExpressionKeyVariableContext) {}
|
||||
|
||||
// EnterForExpressionSource is called when production forExpressionSource is entered.
|
||||
func (s *BaseFqlParserListener) EnterForExpressionSource(ctx *ForExpressionSourceContext) {}
|
||||
|
||||
// ExitForExpressionSource is called when production forExpressionSource is exited.
|
||||
func (s *BaseFqlParserListener) ExitForExpressionSource(ctx *ForExpressionSourceContext) {}
|
||||
|
||||
// EnterForExpressionClause is called when production forExpressionClause is entered.
|
||||
func (s *BaseFqlParserListener) EnterForExpressionClause(ctx *ForExpressionClauseContext) {}
|
||||
|
||||
// ExitForExpressionClause is called when production forExpressionClause is exited.
|
||||
func (s *BaseFqlParserListener) ExitForExpressionClause(ctx *ForExpressionClauseContext) {}
|
||||
|
||||
// EnterFilterClause is called when production filterClause is entered.
|
||||
func (s *BaseFqlParserListener) EnterFilterClause(ctx *FilterClauseContext) {}
|
||||
|
||||
// ExitFilterClause is called when production filterClause is exited.
|
||||
func (s *BaseFqlParserListener) ExitFilterClause(ctx *FilterClauseContext) {}
|
||||
|
||||
// EnterLimitClause is called when production limitClause is entered.
|
||||
func (s *BaseFqlParserListener) EnterLimitClause(ctx *LimitClauseContext) {}
|
||||
|
||||
// ExitLimitClause is called when production limitClause is exited.
|
||||
func (s *BaseFqlParserListener) ExitLimitClause(ctx *LimitClauseContext) {}
|
||||
|
||||
// EnterSortClause is called when production sortClause is entered.
|
||||
func (s *BaseFqlParserListener) EnterSortClause(ctx *SortClauseContext) {}
|
||||
|
||||
// ExitSortClause is called when production sortClause is exited.
|
||||
func (s *BaseFqlParserListener) ExitSortClause(ctx *SortClauseContext) {}
|
||||
|
||||
// EnterSortClauseExpression is called when production sortClauseExpression is entered.
|
||||
func (s *BaseFqlParserListener) EnterSortClauseExpression(ctx *SortClauseExpressionContext) {}
|
||||
|
||||
// ExitSortClauseExpression is called when production sortClauseExpression is exited.
|
||||
func (s *BaseFqlParserListener) ExitSortClauseExpression(ctx *SortClauseExpressionContext) {}
|
||||
|
||||
// EnterCollectClause is called when production collectClause is entered.
|
||||
func (s *BaseFqlParserListener) EnterCollectClause(ctx *CollectClauseContext) {}
|
||||
|
||||
// ExitCollectClause is called when production collectClause is exited.
|
||||
func (s *BaseFqlParserListener) ExitCollectClause(ctx *CollectClauseContext) {}
|
||||
|
||||
// EnterCollectVariable is called when production collectVariable is entered.
|
||||
func (s *BaseFqlParserListener) EnterCollectVariable(ctx *CollectVariableContext) {}
|
||||
|
||||
// ExitCollectVariable is called when production collectVariable is exited.
|
||||
func (s *BaseFqlParserListener) ExitCollectVariable(ctx *CollectVariableContext) {}
|
||||
|
||||
// EnterCollectGroupVariable is called when production collectGroupVariable is entered.
|
||||
func (s *BaseFqlParserListener) EnterCollectGroupVariable(ctx *CollectGroupVariableContext) {}
|
||||
|
||||
// ExitCollectGroupVariable is called when production collectGroupVariable is exited.
|
||||
func (s *BaseFqlParserListener) ExitCollectGroupVariable(ctx *CollectGroupVariableContext) {}
|
||||
|
||||
// EnterCollectKeepVariable is called when production collectKeepVariable is entered.
|
||||
func (s *BaseFqlParserListener) EnterCollectKeepVariable(ctx *CollectKeepVariableContext) {}
|
||||
|
||||
// ExitCollectKeepVariable is called when production collectKeepVariable is exited.
|
||||
func (s *BaseFqlParserListener) ExitCollectKeepVariable(ctx *CollectKeepVariableContext) {}
|
||||
|
||||
// EnterCollectCountVariable is called when production collectCountVariable is entered.
|
||||
func (s *BaseFqlParserListener) EnterCollectCountVariable(ctx *CollectCountVariableContext) {}
|
||||
|
||||
// ExitCollectCountVariable is called when production collectCountVariable is exited.
|
||||
func (s *BaseFqlParserListener) ExitCollectCountVariable(ctx *CollectCountVariableContext) {}
|
||||
|
||||
// EnterCollectAggregateVariable is called when production collectAggregateVariable is entered.
|
||||
func (s *BaseFqlParserListener) EnterCollectAggregateVariable(ctx *CollectAggregateVariableContext) {}
|
||||
|
||||
// ExitCollectAggregateVariable is called when production collectAggregateVariable is exited.
|
||||
func (s *BaseFqlParserListener) ExitCollectAggregateVariable(ctx *CollectAggregateVariableContext) {}
|
||||
|
||||
// EnterCollectAggregateExpression is called when production collectAggregateExpression is entered.
|
||||
func (s *BaseFqlParserListener) EnterCollectAggregateExpression(ctx *CollectAggregateExpressionContext) {
|
||||
}
|
||||
|
||||
// ExitCollectAggregateExpression is called when production collectAggregateExpression is exited.
|
||||
func (s *BaseFqlParserListener) ExitCollectAggregateExpression(ctx *CollectAggregateExpressionContext) {
|
||||
}
|
||||
|
||||
// EnterCollectOption is called when production collectOption is entered.
|
||||
func (s *BaseFqlParserListener) EnterCollectOption(ctx *CollectOptionContext) {}
|
||||
|
||||
// ExitCollectOption is called when production collectOption is exited.
|
||||
func (s *BaseFqlParserListener) ExitCollectOption(ctx *CollectOptionContext) {}
|
||||
|
||||
// EnterForExpressionBody is called when production forExpressionBody is entered.
|
||||
func (s *BaseFqlParserListener) EnterForExpressionBody(ctx *ForExpressionBodyContext) {}
|
||||
|
||||
// ExitForExpressionBody is called when production forExpressionBody is exited.
|
||||
func (s *BaseFqlParserListener) ExitForExpressionBody(ctx *ForExpressionBodyContext) {}
|
||||
|
||||
// EnterForExpressionReturn is called when production forExpressionReturn is entered.
|
||||
func (s *BaseFqlParserListener) EnterForExpressionReturn(ctx *ForExpressionReturnContext) {}
|
||||
|
||||
// ExitForExpressionReturn is called when production forExpressionReturn is exited.
|
||||
func (s *BaseFqlParserListener) ExitForExpressionReturn(ctx *ForExpressionReturnContext) {}
|
||||
|
||||
// EnterVariableDeclaration is called when production variableDeclaration is entered.
|
||||
func (s *BaseFqlParserListener) EnterVariableDeclaration(ctx *VariableDeclarationContext) {}
|
||||
|
||||
// ExitVariableDeclaration is called when production variableDeclaration is exited.
|
||||
func (s *BaseFqlParserListener) ExitVariableDeclaration(ctx *VariableDeclarationContext) {}
|
||||
|
||||
// EnterVariable is called when production variable is entered.
|
||||
func (s *BaseFqlParserListener) EnterVariable(ctx *VariableContext) {}
|
||||
|
||||
// ExitVariable is called when production variable is exited.
|
||||
func (s *BaseFqlParserListener) ExitVariable(ctx *VariableContext) {}
|
||||
|
||||
// EnterArrayLiteral is called when production arrayLiteral is entered.
|
||||
func (s *BaseFqlParserListener) EnterArrayLiteral(ctx *ArrayLiteralContext) {}
|
||||
|
||||
// ExitArrayLiteral is called when production arrayLiteral is exited.
|
||||
func (s *BaseFqlParserListener) ExitArrayLiteral(ctx *ArrayLiteralContext) {}
|
||||
|
||||
// EnterObjectLiteral is called when production objectLiteral is entered.
|
||||
func (s *BaseFqlParserListener) EnterObjectLiteral(ctx *ObjectLiteralContext) {}
|
||||
|
||||
// ExitObjectLiteral is called when production objectLiteral is exited.
|
||||
func (s *BaseFqlParserListener) ExitObjectLiteral(ctx *ObjectLiteralContext) {}
|
||||
|
||||
// EnterBooleanLiteral is called when production booleanLiteral is entered.
|
||||
func (s *BaseFqlParserListener) EnterBooleanLiteral(ctx *BooleanLiteralContext) {}
|
||||
|
||||
// ExitBooleanLiteral is called when production booleanLiteral is exited.
|
||||
func (s *BaseFqlParserListener) ExitBooleanLiteral(ctx *BooleanLiteralContext) {}
|
||||
|
||||
// EnterStringLiteral is called when production stringLiteral is entered.
|
||||
func (s *BaseFqlParserListener) EnterStringLiteral(ctx *StringLiteralContext) {}
|
||||
|
||||
// ExitStringLiteral is called when production stringLiteral is exited.
|
||||
func (s *BaseFqlParserListener) ExitStringLiteral(ctx *StringLiteralContext) {}
|
||||
|
||||
// EnterIntegerLiteral is called when production integerLiteral is entered.
|
||||
func (s *BaseFqlParserListener) EnterIntegerLiteral(ctx *IntegerLiteralContext) {}
|
||||
|
||||
// ExitIntegerLiteral is called when production integerLiteral is exited.
|
||||
func (s *BaseFqlParserListener) ExitIntegerLiteral(ctx *IntegerLiteralContext) {}
|
||||
|
||||
// EnterFloatLiteral is called when production floatLiteral is entered.
|
||||
func (s *BaseFqlParserListener) EnterFloatLiteral(ctx *FloatLiteralContext) {}
|
||||
|
||||
// ExitFloatLiteral is called when production floatLiteral is exited.
|
||||
func (s *BaseFqlParserListener) ExitFloatLiteral(ctx *FloatLiteralContext) {}
|
||||
|
||||
// EnterNoneLiteral is called when production noneLiteral is entered.
|
||||
func (s *BaseFqlParserListener) EnterNoneLiteral(ctx *NoneLiteralContext) {}
|
||||
|
||||
// ExitNoneLiteral is called when production noneLiteral is exited.
|
||||
func (s *BaseFqlParserListener) ExitNoneLiteral(ctx *NoneLiteralContext) {}
|
||||
|
||||
// EnterArrayElementList is called when production arrayElementList is entered.
|
||||
func (s *BaseFqlParserListener) EnterArrayElementList(ctx *ArrayElementListContext) {}
|
||||
|
||||
// ExitArrayElementList is called when production arrayElementList is exited.
|
||||
func (s *BaseFqlParserListener) ExitArrayElementList(ctx *ArrayElementListContext) {}
|
||||
|
||||
// EnterPropertyAssignment is called when production propertyAssignment is entered.
|
||||
func (s *BaseFqlParserListener) EnterPropertyAssignment(ctx *PropertyAssignmentContext) {}
|
||||
|
||||
// ExitPropertyAssignment is called when production propertyAssignment is exited.
|
||||
func (s *BaseFqlParserListener) ExitPropertyAssignment(ctx *PropertyAssignmentContext) {}
|
||||
|
||||
// EnterMemberExpression is called when production memberExpression is entered.
|
||||
func (s *BaseFqlParserListener) EnterMemberExpression(ctx *MemberExpressionContext) {}
|
||||
|
||||
// ExitMemberExpression is called when production memberExpression is exited.
|
||||
func (s *BaseFqlParserListener) ExitMemberExpression(ctx *MemberExpressionContext) {}
|
||||
|
||||
// EnterShorthandPropertyName is called when production shorthandPropertyName is entered.
|
||||
func (s *BaseFqlParserListener) EnterShorthandPropertyName(ctx *ShorthandPropertyNameContext) {}
|
||||
|
||||
// ExitShorthandPropertyName is called when production shorthandPropertyName is exited.
|
||||
func (s *BaseFqlParserListener) ExitShorthandPropertyName(ctx *ShorthandPropertyNameContext) {}
|
||||
|
||||
// EnterComputedPropertyName is called when production computedPropertyName is entered.
|
||||
func (s *BaseFqlParserListener) EnterComputedPropertyName(ctx *ComputedPropertyNameContext) {}
|
||||
|
||||
// ExitComputedPropertyName is called when production computedPropertyName is exited.
|
||||
func (s *BaseFqlParserListener) ExitComputedPropertyName(ctx *ComputedPropertyNameContext) {}
|
||||
|
||||
// EnterPropertyName is called when production propertyName is entered.
|
||||
func (s *BaseFqlParserListener) EnterPropertyName(ctx *PropertyNameContext) {}
|
||||
|
||||
// ExitPropertyName is called when production propertyName is exited.
|
||||
func (s *BaseFqlParserListener) ExitPropertyName(ctx *PropertyNameContext) {}
|
||||
|
||||
// EnterExpressionSequence is called when production expressionSequence is entered.
|
||||
func (s *BaseFqlParserListener) EnterExpressionSequence(ctx *ExpressionSequenceContext) {}
|
||||
|
||||
// ExitExpressionSequence is called when production expressionSequence is exited.
|
||||
func (s *BaseFqlParserListener) ExitExpressionSequence(ctx *ExpressionSequenceContext) {}
|
||||
|
||||
// EnterFunctionCallExpression is called when production functionCallExpression is entered.
|
||||
func (s *BaseFqlParserListener) EnterFunctionCallExpression(ctx *FunctionCallExpressionContext) {}
|
||||
|
||||
// ExitFunctionCallExpression is called when production functionCallExpression is exited.
|
||||
func (s *BaseFqlParserListener) ExitFunctionCallExpression(ctx *FunctionCallExpressionContext) {}
|
||||
|
||||
// EnterArguments is called when production arguments is entered.
|
||||
func (s *BaseFqlParserListener) EnterArguments(ctx *ArgumentsContext) {}
|
||||
|
||||
// ExitArguments is called when production arguments is exited.
|
||||
func (s *BaseFqlParserListener) ExitArguments(ctx *ArgumentsContext) {}
|
||||
|
||||
// EnterExpression is called when production expression is entered.
|
||||
func (s *BaseFqlParserListener) EnterExpression(ctx *ExpressionContext) {}
|
||||
|
||||
// ExitExpression is called when production expression is exited.
|
||||
func (s *BaseFqlParserListener) ExitExpression(ctx *ExpressionContext) {}
|
||||
|
||||
// EnterReservedWord is called when production reservedWord is entered.
|
||||
func (s *BaseFqlParserListener) EnterReservedWord(ctx *ReservedWordContext) {}
|
||||
|
||||
// ExitReservedWord is called when production reservedWord is exited.
|
||||
func (s *BaseFqlParserListener) ExitReservedWord(ctx *ReservedWordContext) {}
|
||||
|
||||
// EnterKeyword is called when production keyword is entered.
|
||||
func (s *BaseFqlParserListener) EnterKeyword(ctx *KeywordContext) {}
|
||||
|
||||
// ExitKeyword is called when production keyword is exited.
|
||||
func (s *BaseFqlParserListener) ExitKeyword(ctx *KeywordContext) {}
|
||||
|
||||
// EnterEqualityOperator is called when production equalityOperator is entered.
|
||||
func (s *BaseFqlParserListener) EnterEqualityOperator(ctx *EqualityOperatorContext) {}
|
||||
|
||||
// ExitEqualityOperator is called when production equalityOperator is exited.
|
||||
func (s *BaseFqlParserListener) ExitEqualityOperator(ctx *EqualityOperatorContext) {}
|
||||
|
||||
// EnterLogicalOperator is called when production logicalOperator is entered.
|
||||
func (s *BaseFqlParserListener) EnterLogicalOperator(ctx *LogicalOperatorContext) {}
|
||||
|
||||
// ExitLogicalOperator is called when production logicalOperator is exited.
|
||||
func (s *BaseFqlParserListener) ExitLogicalOperator(ctx *LogicalOperatorContext) {}
|
||||
|
||||
// EnterMathOperator is called when production mathOperator is entered.
|
||||
func (s *BaseFqlParserListener) EnterMathOperator(ctx *MathOperatorContext) {}
|
||||
|
||||
// ExitMathOperator is called when production mathOperator is exited.
|
||||
func (s *BaseFqlParserListener) ExitMathOperator(ctx *MathOperatorContext) {}
|
||||
|
||||
// EnterUnaryOperator is called when production unaryOperator is entered.
|
||||
func (s *BaseFqlParserListener) EnterUnaryOperator(ctx *UnaryOperatorContext) {}
|
||||
|
||||
// ExitUnaryOperator is called when production unaryOperator is exited.
|
||||
func (s *BaseFqlParserListener) ExitUnaryOperator(ctx *UnaryOperatorContext) {}
|
204
pkg/parser/fql/fqlparser_base_visitor.go
Normal file
204
pkg/parser/fql/fqlparser_base_visitor.go
Normal file
@@ -0,0 +1,204 @@
|
||||
// Code generated from antlr/FqlParser.g4 by ANTLR 4.7.1. DO NOT EDIT.
|
||||
|
||||
package fql // FqlParser
|
||||
import "github.com/antlr/antlr4/runtime/Go/antlr"
|
||||
|
||||
type BaseFqlParserVisitor struct {
|
||||
*antlr.BaseParseTreeVisitor
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitProgram(ctx *ProgramContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitBody(ctx *BodyContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitBodyStatement(ctx *BodyStatementContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitBodyExpression(ctx *BodyExpressionContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitReturnExpression(ctx *ReturnExpressionContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitForExpression(ctx *ForExpressionContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitForExpressionValueVariable(ctx *ForExpressionValueVariableContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitForExpressionKeyVariable(ctx *ForExpressionKeyVariableContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitForExpressionSource(ctx *ForExpressionSourceContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitForExpressionClause(ctx *ForExpressionClauseContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitFilterClause(ctx *FilterClauseContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitLimitClause(ctx *LimitClauseContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitSortClause(ctx *SortClauseContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitSortClauseExpression(ctx *SortClauseExpressionContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitCollectClause(ctx *CollectClauseContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitCollectVariable(ctx *CollectVariableContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitCollectGroupVariable(ctx *CollectGroupVariableContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitCollectKeepVariable(ctx *CollectKeepVariableContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitCollectCountVariable(ctx *CollectCountVariableContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitCollectAggregateVariable(ctx *CollectAggregateVariableContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitCollectAggregateExpression(ctx *CollectAggregateExpressionContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitCollectOption(ctx *CollectOptionContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitForExpressionBody(ctx *ForExpressionBodyContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitForExpressionReturn(ctx *ForExpressionReturnContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitVariableDeclaration(ctx *VariableDeclarationContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitVariable(ctx *VariableContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitArrayLiteral(ctx *ArrayLiteralContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitObjectLiteral(ctx *ObjectLiteralContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitBooleanLiteral(ctx *BooleanLiteralContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitStringLiteral(ctx *StringLiteralContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitIntegerLiteral(ctx *IntegerLiteralContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitFloatLiteral(ctx *FloatLiteralContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitNoneLiteral(ctx *NoneLiteralContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitArrayElementList(ctx *ArrayElementListContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitPropertyAssignment(ctx *PropertyAssignmentContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitMemberExpression(ctx *MemberExpressionContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitShorthandPropertyName(ctx *ShorthandPropertyNameContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitComputedPropertyName(ctx *ComputedPropertyNameContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitPropertyName(ctx *PropertyNameContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitExpressionSequence(ctx *ExpressionSequenceContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitFunctionCallExpression(ctx *FunctionCallExpressionContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitArguments(ctx *ArgumentsContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitExpression(ctx *ExpressionContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitReservedWord(ctx *ReservedWordContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitKeyword(ctx *KeywordContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitEqualityOperator(ctx *EqualityOperatorContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitLogicalOperator(ctx *LogicalOperatorContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitMathOperator(ctx *MathOperatorContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitUnaryOperator(ctx *UnaryOperatorContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
303
pkg/parser/fql/fqlparser_listener.go
Normal file
303
pkg/parser/fql/fqlparser_listener.go
Normal file
@@ -0,0 +1,303 @@
|
||||
// Code generated from antlr/FqlParser.g4 by ANTLR 4.7.1. DO NOT EDIT.
|
||||
|
||||
package fql // FqlParser
|
||||
import "github.com/antlr/antlr4/runtime/Go/antlr"
|
||||
|
||||
// FqlParserListener is a complete listener for a parse tree produced by FqlParser.
|
||||
type FqlParserListener interface {
|
||||
antlr.ParseTreeListener
|
||||
|
||||
// EnterProgram is called when entering the program production.
|
||||
EnterProgram(c *ProgramContext)
|
||||
|
||||
// EnterBody is called when entering the body production.
|
||||
EnterBody(c *BodyContext)
|
||||
|
||||
// EnterBodyStatement is called when entering the bodyStatement production.
|
||||
EnterBodyStatement(c *BodyStatementContext)
|
||||
|
||||
// EnterBodyExpression is called when entering the bodyExpression production.
|
||||
EnterBodyExpression(c *BodyExpressionContext)
|
||||
|
||||
// EnterReturnExpression is called when entering the returnExpression production.
|
||||
EnterReturnExpression(c *ReturnExpressionContext)
|
||||
|
||||
// EnterForExpression is called when entering the forExpression production.
|
||||
EnterForExpression(c *ForExpressionContext)
|
||||
|
||||
// EnterForExpressionValueVariable is called when entering the forExpressionValueVariable production.
|
||||
EnterForExpressionValueVariable(c *ForExpressionValueVariableContext)
|
||||
|
||||
// EnterForExpressionKeyVariable is called when entering the forExpressionKeyVariable production.
|
||||
EnterForExpressionKeyVariable(c *ForExpressionKeyVariableContext)
|
||||
|
||||
// EnterForExpressionSource is called when entering the forExpressionSource production.
|
||||
EnterForExpressionSource(c *ForExpressionSourceContext)
|
||||
|
||||
// EnterForExpressionClause is called when entering the forExpressionClause production.
|
||||
EnterForExpressionClause(c *ForExpressionClauseContext)
|
||||
|
||||
// EnterFilterClause is called when entering the filterClause production.
|
||||
EnterFilterClause(c *FilterClauseContext)
|
||||
|
||||
// EnterLimitClause is called when entering the limitClause production.
|
||||
EnterLimitClause(c *LimitClauseContext)
|
||||
|
||||
// EnterSortClause is called when entering the sortClause production.
|
||||
EnterSortClause(c *SortClauseContext)
|
||||
|
||||
// EnterSortClauseExpression is called when entering the sortClauseExpression production.
|
||||
EnterSortClauseExpression(c *SortClauseExpressionContext)
|
||||
|
||||
// EnterCollectClause is called when entering the collectClause production.
|
||||
EnterCollectClause(c *CollectClauseContext)
|
||||
|
||||
// EnterCollectVariable is called when entering the collectVariable production.
|
||||
EnterCollectVariable(c *CollectVariableContext)
|
||||
|
||||
// EnterCollectGroupVariable is called when entering the collectGroupVariable production.
|
||||
EnterCollectGroupVariable(c *CollectGroupVariableContext)
|
||||
|
||||
// EnterCollectKeepVariable is called when entering the collectKeepVariable production.
|
||||
EnterCollectKeepVariable(c *CollectKeepVariableContext)
|
||||
|
||||
// EnterCollectCountVariable is called when entering the collectCountVariable production.
|
||||
EnterCollectCountVariable(c *CollectCountVariableContext)
|
||||
|
||||
// EnterCollectAggregateVariable is called when entering the collectAggregateVariable production.
|
||||
EnterCollectAggregateVariable(c *CollectAggregateVariableContext)
|
||||
|
||||
// EnterCollectAggregateExpression is called when entering the collectAggregateExpression production.
|
||||
EnterCollectAggregateExpression(c *CollectAggregateExpressionContext)
|
||||
|
||||
// EnterCollectOption is called when entering the collectOption production.
|
||||
EnterCollectOption(c *CollectOptionContext)
|
||||
|
||||
// EnterForExpressionBody is called when entering the forExpressionBody production.
|
||||
EnterForExpressionBody(c *ForExpressionBodyContext)
|
||||
|
||||
// EnterForExpressionReturn is called when entering the forExpressionReturn production.
|
||||
EnterForExpressionReturn(c *ForExpressionReturnContext)
|
||||
|
||||
// EnterVariableDeclaration is called when entering the variableDeclaration production.
|
||||
EnterVariableDeclaration(c *VariableDeclarationContext)
|
||||
|
||||
// EnterVariable is called when entering the variable production.
|
||||
EnterVariable(c *VariableContext)
|
||||
|
||||
// EnterArrayLiteral is called when entering the arrayLiteral production.
|
||||
EnterArrayLiteral(c *ArrayLiteralContext)
|
||||
|
||||
// EnterObjectLiteral is called when entering the objectLiteral production.
|
||||
EnterObjectLiteral(c *ObjectLiteralContext)
|
||||
|
||||
// EnterBooleanLiteral is called when entering the booleanLiteral production.
|
||||
EnterBooleanLiteral(c *BooleanLiteralContext)
|
||||
|
||||
// EnterStringLiteral is called when entering the stringLiteral production.
|
||||
EnterStringLiteral(c *StringLiteralContext)
|
||||
|
||||
// EnterIntegerLiteral is called when entering the integerLiteral production.
|
||||
EnterIntegerLiteral(c *IntegerLiteralContext)
|
||||
|
||||
// EnterFloatLiteral is called when entering the floatLiteral production.
|
||||
EnterFloatLiteral(c *FloatLiteralContext)
|
||||
|
||||
// EnterNoneLiteral is called when entering the noneLiteral production.
|
||||
EnterNoneLiteral(c *NoneLiteralContext)
|
||||
|
||||
// EnterArrayElementList is called when entering the arrayElementList production.
|
||||
EnterArrayElementList(c *ArrayElementListContext)
|
||||
|
||||
// EnterPropertyAssignment is called when entering the propertyAssignment production.
|
||||
EnterPropertyAssignment(c *PropertyAssignmentContext)
|
||||
|
||||
// EnterMemberExpression is called when entering the memberExpression production.
|
||||
EnterMemberExpression(c *MemberExpressionContext)
|
||||
|
||||
// EnterShorthandPropertyName is called when entering the shorthandPropertyName production.
|
||||
EnterShorthandPropertyName(c *ShorthandPropertyNameContext)
|
||||
|
||||
// EnterComputedPropertyName is called when entering the computedPropertyName production.
|
||||
EnterComputedPropertyName(c *ComputedPropertyNameContext)
|
||||
|
||||
// EnterPropertyName is called when entering the propertyName production.
|
||||
EnterPropertyName(c *PropertyNameContext)
|
||||
|
||||
// EnterExpressionSequence is called when entering the expressionSequence production.
|
||||
EnterExpressionSequence(c *ExpressionSequenceContext)
|
||||
|
||||
// EnterFunctionCallExpression is called when entering the functionCallExpression production.
|
||||
EnterFunctionCallExpression(c *FunctionCallExpressionContext)
|
||||
|
||||
// EnterArguments is called when entering the arguments production.
|
||||
EnterArguments(c *ArgumentsContext)
|
||||
|
||||
// EnterExpression is called when entering the expression production.
|
||||
EnterExpression(c *ExpressionContext)
|
||||
|
||||
// EnterReservedWord is called when entering the reservedWord production.
|
||||
EnterReservedWord(c *ReservedWordContext)
|
||||
|
||||
// EnterKeyword is called when entering the keyword production.
|
||||
EnterKeyword(c *KeywordContext)
|
||||
|
||||
// EnterEqualityOperator is called when entering the equalityOperator production.
|
||||
EnterEqualityOperator(c *EqualityOperatorContext)
|
||||
|
||||
// EnterLogicalOperator is called when entering the logicalOperator production.
|
||||
EnterLogicalOperator(c *LogicalOperatorContext)
|
||||
|
||||
// EnterMathOperator is called when entering the mathOperator production.
|
||||
EnterMathOperator(c *MathOperatorContext)
|
||||
|
||||
// EnterUnaryOperator is called when entering the unaryOperator production.
|
||||
EnterUnaryOperator(c *UnaryOperatorContext)
|
||||
|
||||
// ExitProgram is called when exiting the program production.
|
||||
ExitProgram(c *ProgramContext)
|
||||
|
||||
// ExitBody is called when exiting the body production.
|
||||
ExitBody(c *BodyContext)
|
||||
|
||||
// ExitBodyStatement is called when exiting the bodyStatement production.
|
||||
ExitBodyStatement(c *BodyStatementContext)
|
||||
|
||||
// ExitBodyExpression is called when exiting the bodyExpression production.
|
||||
ExitBodyExpression(c *BodyExpressionContext)
|
||||
|
||||
// ExitReturnExpression is called when exiting the returnExpression production.
|
||||
ExitReturnExpression(c *ReturnExpressionContext)
|
||||
|
||||
// ExitForExpression is called when exiting the forExpression production.
|
||||
ExitForExpression(c *ForExpressionContext)
|
||||
|
||||
// ExitForExpressionValueVariable is called when exiting the forExpressionValueVariable production.
|
||||
ExitForExpressionValueVariable(c *ForExpressionValueVariableContext)
|
||||
|
||||
// ExitForExpressionKeyVariable is called when exiting the forExpressionKeyVariable production.
|
||||
ExitForExpressionKeyVariable(c *ForExpressionKeyVariableContext)
|
||||
|
||||
// ExitForExpressionSource is called when exiting the forExpressionSource production.
|
||||
ExitForExpressionSource(c *ForExpressionSourceContext)
|
||||
|
||||
// ExitForExpressionClause is called when exiting the forExpressionClause production.
|
||||
ExitForExpressionClause(c *ForExpressionClauseContext)
|
||||
|
||||
// ExitFilterClause is called when exiting the filterClause production.
|
||||
ExitFilterClause(c *FilterClauseContext)
|
||||
|
||||
// ExitLimitClause is called when exiting the limitClause production.
|
||||
ExitLimitClause(c *LimitClauseContext)
|
||||
|
||||
// ExitSortClause is called when exiting the sortClause production.
|
||||
ExitSortClause(c *SortClauseContext)
|
||||
|
||||
// ExitSortClauseExpression is called when exiting the sortClauseExpression production.
|
||||
ExitSortClauseExpression(c *SortClauseExpressionContext)
|
||||
|
||||
// ExitCollectClause is called when exiting the collectClause production.
|
||||
ExitCollectClause(c *CollectClauseContext)
|
||||
|
||||
// ExitCollectVariable is called when exiting the collectVariable production.
|
||||
ExitCollectVariable(c *CollectVariableContext)
|
||||
|
||||
// ExitCollectGroupVariable is called when exiting the collectGroupVariable production.
|
||||
ExitCollectGroupVariable(c *CollectGroupVariableContext)
|
||||
|
||||
// ExitCollectKeepVariable is called when exiting the collectKeepVariable production.
|
||||
ExitCollectKeepVariable(c *CollectKeepVariableContext)
|
||||
|
||||
// ExitCollectCountVariable is called when exiting the collectCountVariable production.
|
||||
ExitCollectCountVariable(c *CollectCountVariableContext)
|
||||
|
||||
// ExitCollectAggregateVariable is called when exiting the collectAggregateVariable production.
|
||||
ExitCollectAggregateVariable(c *CollectAggregateVariableContext)
|
||||
|
||||
// ExitCollectAggregateExpression is called when exiting the collectAggregateExpression production.
|
||||
ExitCollectAggregateExpression(c *CollectAggregateExpressionContext)
|
||||
|
||||
// ExitCollectOption is called when exiting the collectOption production.
|
||||
ExitCollectOption(c *CollectOptionContext)
|
||||
|
||||
// ExitForExpressionBody is called when exiting the forExpressionBody production.
|
||||
ExitForExpressionBody(c *ForExpressionBodyContext)
|
||||
|
||||
// ExitForExpressionReturn is called when exiting the forExpressionReturn production.
|
||||
ExitForExpressionReturn(c *ForExpressionReturnContext)
|
||||
|
||||
// ExitVariableDeclaration is called when exiting the variableDeclaration production.
|
||||
ExitVariableDeclaration(c *VariableDeclarationContext)
|
||||
|
||||
// ExitVariable is called when exiting the variable production.
|
||||
ExitVariable(c *VariableContext)
|
||||
|
||||
// ExitArrayLiteral is called when exiting the arrayLiteral production.
|
||||
ExitArrayLiteral(c *ArrayLiteralContext)
|
||||
|
||||
// ExitObjectLiteral is called when exiting the objectLiteral production.
|
||||
ExitObjectLiteral(c *ObjectLiteralContext)
|
||||
|
||||
// ExitBooleanLiteral is called when exiting the booleanLiteral production.
|
||||
ExitBooleanLiteral(c *BooleanLiteralContext)
|
||||
|
||||
// ExitStringLiteral is called when exiting the stringLiteral production.
|
||||
ExitStringLiteral(c *StringLiteralContext)
|
||||
|
||||
// ExitIntegerLiteral is called when exiting the integerLiteral production.
|
||||
ExitIntegerLiteral(c *IntegerLiteralContext)
|
||||
|
||||
// ExitFloatLiteral is called when exiting the floatLiteral production.
|
||||
ExitFloatLiteral(c *FloatLiteralContext)
|
||||
|
||||
// ExitNoneLiteral is called when exiting the noneLiteral production.
|
||||
ExitNoneLiteral(c *NoneLiteralContext)
|
||||
|
||||
// ExitArrayElementList is called when exiting the arrayElementList production.
|
||||
ExitArrayElementList(c *ArrayElementListContext)
|
||||
|
||||
// ExitPropertyAssignment is called when exiting the propertyAssignment production.
|
||||
ExitPropertyAssignment(c *PropertyAssignmentContext)
|
||||
|
||||
// ExitMemberExpression is called when exiting the memberExpression production.
|
||||
ExitMemberExpression(c *MemberExpressionContext)
|
||||
|
||||
// ExitShorthandPropertyName is called when exiting the shorthandPropertyName production.
|
||||
ExitShorthandPropertyName(c *ShorthandPropertyNameContext)
|
||||
|
||||
// ExitComputedPropertyName is called when exiting the computedPropertyName production.
|
||||
ExitComputedPropertyName(c *ComputedPropertyNameContext)
|
||||
|
||||
// ExitPropertyName is called when exiting the propertyName production.
|
||||
ExitPropertyName(c *PropertyNameContext)
|
||||
|
||||
// ExitExpressionSequence is called when exiting the expressionSequence production.
|
||||
ExitExpressionSequence(c *ExpressionSequenceContext)
|
||||
|
||||
// ExitFunctionCallExpression is called when exiting the functionCallExpression production.
|
||||
ExitFunctionCallExpression(c *FunctionCallExpressionContext)
|
||||
|
||||
// ExitArguments is called when exiting the arguments production.
|
||||
ExitArguments(c *ArgumentsContext)
|
||||
|
||||
// ExitExpression is called when exiting the expression production.
|
||||
ExitExpression(c *ExpressionContext)
|
||||
|
||||
// ExitReservedWord is called when exiting the reservedWord production.
|
||||
ExitReservedWord(c *ReservedWordContext)
|
||||
|
||||
// ExitKeyword is called when exiting the keyword production.
|
||||
ExitKeyword(c *KeywordContext)
|
||||
|
||||
// ExitEqualityOperator is called when exiting the equalityOperator production.
|
||||
ExitEqualityOperator(c *EqualityOperatorContext)
|
||||
|
||||
// ExitLogicalOperator is called when exiting the logicalOperator production.
|
||||
ExitLogicalOperator(c *LogicalOperatorContext)
|
||||
|
||||
// ExitMathOperator is called when exiting the mathOperator production.
|
||||
ExitMathOperator(c *MathOperatorContext)
|
||||
|
||||
// ExitUnaryOperator is called when exiting the unaryOperator production.
|
||||
ExitUnaryOperator(c *UnaryOperatorContext)
|
||||
}
|
156
pkg/parser/fql/fqlparser_visitor.go
Normal file
156
pkg/parser/fql/fqlparser_visitor.go
Normal file
@@ -0,0 +1,156 @@
|
||||
// Code generated from antlr/FqlParser.g4 by ANTLR 4.7.1. DO NOT EDIT.
|
||||
|
||||
package fql // FqlParser
|
||||
import "github.com/antlr/antlr4/runtime/Go/antlr"
|
||||
|
||||
// A complete Visitor for a parse tree produced by FqlParser.
|
||||
type FqlParserVisitor interface {
|
||||
antlr.ParseTreeVisitor
|
||||
|
||||
// Visit a parse tree produced by FqlParser#program.
|
||||
VisitProgram(ctx *ProgramContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#body.
|
||||
VisitBody(ctx *BodyContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#bodyStatement.
|
||||
VisitBodyStatement(ctx *BodyStatementContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#bodyExpression.
|
||||
VisitBodyExpression(ctx *BodyExpressionContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#returnExpression.
|
||||
VisitReturnExpression(ctx *ReturnExpressionContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#forExpression.
|
||||
VisitForExpression(ctx *ForExpressionContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#forExpressionValueVariable.
|
||||
VisitForExpressionValueVariable(ctx *ForExpressionValueVariableContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#forExpressionKeyVariable.
|
||||
VisitForExpressionKeyVariable(ctx *ForExpressionKeyVariableContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#forExpressionSource.
|
||||
VisitForExpressionSource(ctx *ForExpressionSourceContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#forExpressionClause.
|
||||
VisitForExpressionClause(ctx *ForExpressionClauseContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#filterClause.
|
||||
VisitFilterClause(ctx *FilterClauseContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#limitClause.
|
||||
VisitLimitClause(ctx *LimitClauseContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#sortClause.
|
||||
VisitSortClause(ctx *SortClauseContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#sortClauseExpression.
|
||||
VisitSortClauseExpression(ctx *SortClauseExpressionContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#collectClause.
|
||||
VisitCollectClause(ctx *CollectClauseContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#collectVariable.
|
||||
VisitCollectVariable(ctx *CollectVariableContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#collectGroupVariable.
|
||||
VisitCollectGroupVariable(ctx *CollectGroupVariableContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#collectKeepVariable.
|
||||
VisitCollectKeepVariable(ctx *CollectKeepVariableContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#collectCountVariable.
|
||||
VisitCollectCountVariable(ctx *CollectCountVariableContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#collectAggregateVariable.
|
||||
VisitCollectAggregateVariable(ctx *CollectAggregateVariableContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#collectAggregateExpression.
|
||||
VisitCollectAggregateExpression(ctx *CollectAggregateExpressionContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#collectOption.
|
||||
VisitCollectOption(ctx *CollectOptionContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#forExpressionBody.
|
||||
VisitForExpressionBody(ctx *ForExpressionBodyContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#forExpressionReturn.
|
||||
VisitForExpressionReturn(ctx *ForExpressionReturnContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#variableDeclaration.
|
||||
VisitVariableDeclaration(ctx *VariableDeclarationContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#variable.
|
||||
VisitVariable(ctx *VariableContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#arrayLiteral.
|
||||
VisitArrayLiteral(ctx *ArrayLiteralContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#objectLiteral.
|
||||
VisitObjectLiteral(ctx *ObjectLiteralContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#booleanLiteral.
|
||||
VisitBooleanLiteral(ctx *BooleanLiteralContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#stringLiteral.
|
||||
VisitStringLiteral(ctx *StringLiteralContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#integerLiteral.
|
||||
VisitIntegerLiteral(ctx *IntegerLiteralContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#floatLiteral.
|
||||
VisitFloatLiteral(ctx *FloatLiteralContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#noneLiteral.
|
||||
VisitNoneLiteral(ctx *NoneLiteralContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#arrayElementList.
|
||||
VisitArrayElementList(ctx *ArrayElementListContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#propertyAssignment.
|
||||
VisitPropertyAssignment(ctx *PropertyAssignmentContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#memberExpression.
|
||||
VisitMemberExpression(ctx *MemberExpressionContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#shorthandPropertyName.
|
||||
VisitShorthandPropertyName(ctx *ShorthandPropertyNameContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#computedPropertyName.
|
||||
VisitComputedPropertyName(ctx *ComputedPropertyNameContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#propertyName.
|
||||
VisitPropertyName(ctx *PropertyNameContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#expressionSequence.
|
||||
VisitExpressionSequence(ctx *ExpressionSequenceContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#functionCallExpression.
|
||||
VisitFunctionCallExpression(ctx *FunctionCallExpressionContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#arguments.
|
||||
VisitArguments(ctx *ArgumentsContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#expression.
|
||||
VisitExpression(ctx *ExpressionContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#reservedWord.
|
||||
VisitReservedWord(ctx *ReservedWordContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#keyword.
|
||||
VisitKeyword(ctx *KeywordContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#equalityOperator.
|
||||
VisitEqualityOperator(ctx *EqualityOperatorContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#logicalOperator.
|
||||
VisitLogicalOperator(ctx *LogicalOperatorContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#mathOperator.
|
||||
VisitMathOperator(ctx *MathOperatorContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#unaryOperator.
|
||||
VisitUnaryOperator(ctx *UnaryOperatorContext) interface{}
|
||||
}
|
31
pkg/parser/parser.go
Normal file
31
pkg/parser/parser.go
Normal file
@@ -0,0 +1,31 @@
|
||||
//go:generate antlr4 -Xexact-output-dir -o fql -package fql -visitor -Dlanguage=Go antlr/FqlLexer.g4 antlr/FqlParser.g4
|
||||
package parser
|
||||
|
||||
import (
|
||||
"github.com/MontFerret/ferret/pkg/parser/fql"
|
||||
"github.com/antlr/antlr4/runtime/Go/antlr"
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
tree *fql.FqlParser
|
||||
}
|
||||
|
||||
func New(query string) *Parser {
|
||||
input := antlr.NewInputStream(query)
|
||||
lexer := fql.NewFqlLexer(input)
|
||||
stream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)
|
||||
|
||||
p := fql.NewFqlParser(stream)
|
||||
p.BuildParseTrees = true
|
||||
p.AddErrorListener(antlr.NewDiagnosticErrorListener(true))
|
||||
|
||||
return &Parser{tree: p}
|
||||
}
|
||||
|
||||
func (p *Parser) AddErrorListener(listener antlr.ErrorListener) {
|
||||
p.tree.AddErrorListener(listener)
|
||||
}
|
||||
|
||||
func (p *Parser) Visit(visitor fql.FqlParserVisitor) interface{} {
|
||||
return visitor.VisitProgram(p.tree.Program().(*fql.ProgramContext))
|
||||
}
|
7
pkg/runtime/collections/collection.go
Normal file
7
pkg/runtime/collections/collection.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package collections
|
||||
|
||||
import "github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
|
||||
type Collection interface {
|
||||
Length() values.Int
|
||||
}
|
9
pkg/runtime/collections/errors.go
Normal file
9
pkg/runtime/collections/errors.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrExhausted = core.Error(core.ErrInvalidOperation, "iterator has been exhausted")
|
||||
)
|
84
pkg/runtime/collections/filter.go
Normal file
84
pkg/runtime/collections/filter.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
FilterPredicate func(val core.Value, key core.Value) (bool, error)
|
||||
FilterIterator struct {
|
||||
src Iterator
|
||||
predicate FilterPredicate
|
||||
value core.Value
|
||||
key core.Value
|
||||
ready bool
|
||||
}
|
||||
)
|
||||
|
||||
func NewFilterIterator(src Iterator, predicate FilterPredicate) (*FilterIterator, error) {
|
||||
if core.IsNil(src) {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "source")
|
||||
}
|
||||
|
||||
if core.IsNil(predicate) {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "predicate")
|
||||
}
|
||||
|
||||
return &FilterIterator{src: src, predicate: predicate}, nil
|
||||
}
|
||||
|
||||
func (iterator *FilterIterator) HasNext() bool {
|
||||
if !iterator.ready {
|
||||
iterator.filter()
|
||||
iterator.ready = true
|
||||
}
|
||||
|
||||
return iterator.value != nil && iterator.value.Type() != core.NoneType
|
||||
}
|
||||
|
||||
func (iterator *FilterIterator) Next() (core.Value, core.Value, error) {
|
||||
if iterator.HasNext() == true {
|
||||
val := iterator.value
|
||||
key := iterator.key
|
||||
|
||||
iterator.filter()
|
||||
|
||||
return val, key, nil
|
||||
}
|
||||
|
||||
return values.None, values.None, ErrExhausted
|
||||
}
|
||||
|
||||
func (iterator *FilterIterator) filter() {
|
||||
var doNext bool
|
||||
|
||||
for iterator.src.HasNext() {
|
||||
val, key, err := iterator.src.Next()
|
||||
|
||||
if err != nil {
|
||||
doNext = false
|
||||
break
|
||||
}
|
||||
|
||||
take, err := iterator.predicate(val, key)
|
||||
|
||||
if err != nil {
|
||||
doNext = false
|
||||
break
|
||||
}
|
||||
|
||||
if take == true {
|
||||
doNext = true
|
||||
iterator.value = val
|
||||
iterator.key = key
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if doNext == false {
|
||||
iterator.value = nil
|
||||
iterator.key = nil
|
||||
}
|
||||
}
|
231
pkg/runtime/collections/filter_test.go
Normal file
231
pkg/runtime/collections/filter_test.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"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"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
Convey("Should filter out non-even values", t, func() {
|
||||
arr := []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
predicate := func(val core.Value, _ core.Value) (bool, error) {
|
||||
i := float64(val.Unwrap().(int))
|
||||
calc := float64(i / 2)
|
||||
|
||||
return calc == math.Floor(calc), nil
|
||||
}
|
||||
|
||||
iter, err := collections.NewFilterIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
predicate,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := iter.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
So(res, ShouldHaveLength, 2)
|
||||
})
|
||||
|
||||
Convey("Should filter out non-even keys", t, func() {
|
||||
arr := []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
predicate := func(_ core.Value, key core.Value) (bool, error) {
|
||||
i := float64(key.Unwrap().(int))
|
||||
|
||||
if i == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
calc := float64(i / 2)
|
||||
|
||||
return calc == math.Floor(calc), nil
|
||||
}
|
||||
|
||||
iter, err := collections.NewFilterIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
predicate,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := iter.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
So(res, ShouldHaveLength, 2)
|
||||
})
|
||||
|
||||
Convey("Should filter out values all values", t, func() {
|
||||
arr := []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
predicate := func(val core.Value, _ core.Value) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
iter, err := collections.NewFilterIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
predicate,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := iter.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
So(res, ShouldHaveLength, 0)
|
||||
})
|
||||
|
||||
Convey("Should pass through all values", t, func() {
|
||||
arr := []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
predicate := func(val core.Value, _ core.Value) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
iter, err := collections.NewFilterIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
predicate,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := iter.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
So(res, ShouldHaveLength, len(arr))
|
||||
})
|
||||
|
||||
Convey("Should return an error when exhausted", t, func() {
|
||||
arr := []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
predicate := func(val core.Value, _ core.Value) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
iter, err := collections.NewFilterIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
predicate,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := iter.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
_, _, err = iter.Next()
|
||||
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Should iterate over nested filter", t, func() {
|
||||
arr := []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
// i < 5
|
||||
predicate1 := func(val core.Value, _ core.Value) (bool, error) {
|
||||
return val.Compare(values.NewInt(5)) == -1, nil
|
||||
}
|
||||
|
||||
// i > 2
|
||||
predicate2 := func(val core.Value, _ core.Value) (bool, error) {
|
||||
return val.Compare(values.NewInt(2)) == 1, nil
|
||||
}
|
||||
|
||||
it, _ := collections.NewFilterIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
predicate1,
|
||||
)
|
||||
|
||||
iter, err := collections.NewFilterIterator(
|
||||
it,
|
||||
predicate2,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res, err := collections.ToSlice(iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
js, _ := json.Marshal(res)
|
||||
|
||||
So(string(js), ShouldEqual, `[3,4]`)
|
||||
})
|
||||
}
|
53
pkg/runtime/collections/goup_test.go
Normal file
53
pkg/runtime/collections/goup_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"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 TestGroup(t *testing.T) {
|
||||
makeObj := func(active bool, age int, city, gender string) *values.Object {
|
||||
obj := values.NewObject()
|
||||
|
||||
obj.Set("active", values.NewBoolean(active))
|
||||
obj.Set("age", values.NewInt(age))
|
||||
obj.Set("city", values.NewString(city))
|
||||
obj.Set("gender", values.NewString(gender))
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
Convey("Should group by a single key", t, func() {
|
||||
arr := []core.Value{
|
||||
makeObj(true, 31, "D.C.", "m"),
|
||||
makeObj(true, 29, "L.A.", "f"),
|
||||
makeObj(true, 36, "D.C.", "m"),
|
||||
makeObj(true, 34, "N.Y.C.", "f"),
|
||||
makeObj(true, 28, "L.A.", "f"),
|
||||
makeObj(true, 41, "Boston", "m"),
|
||||
}
|
||||
|
||||
iter, err := collections.NewGroupIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
func(value core.Value) (core.Value, error) {
|
||||
val, _ := value.(*values.Object).Get("gender")
|
||||
|
||||
return val, nil
|
||||
},
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res, err := collections.ToMap(iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
j, _ := json.Marshal(res)
|
||||
|
||||
So(string(j), ShouldEqual, `{"f":[{"active":true,"age":29,"city":"L.A.","gender":"f"},{"active":true,"age":34,"city":"N.Y.C.","gender":"f"},{"active":true,"age":28,"city":"L.A.","gender":"f"}],"m":[{"active":true,"age":31,"city":"D.C.","gender":"m"},{"active":true,"age":36,"city":"D.C.","gender":"m"},{"active":true,"age":41,"city":"Boston","gender":"m"}]}`)
|
||||
})
|
||||
}
|
85
pkg/runtime/collections/group.go
Normal file
85
pkg/runtime/collections/group.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
type (
|
||||
GroupKey func(value core.Value) (core.Value, error)
|
||||
GroupIterator struct {
|
||||
src Iterator
|
||||
keys []GroupKey
|
||||
ready bool
|
||||
values *MapIterator
|
||||
}
|
||||
)
|
||||
|
||||
func NewGroupIterator(
|
||||
src Iterator,
|
||||
keys ...GroupKey,
|
||||
) (*GroupIterator, error) {
|
||||
if core.IsNil(src) {
|
||||
return nil, core.Error(core.ErrMissedArgument, "source")
|
||||
}
|
||||
|
||||
if len(keys) == 0 {
|
||||
return nil, core.Error(core.ErrMissedArgument, "key(s)")
|
||||
}
|
||||
|
||||
return &GroupIterator{src, keys, false, nil}, nil
|
||||
}
|
||||
|
||||
func (iterator *GroupIterator) HasNext() bool {
|
||||
if !iterator.ready {
|
||||
iterator.ready = true
|
||||
groups, err := iterator.group()
|
||||
|
||||
if err != nil {
|
||||
iterator.values = NewMapIterator(map[string]core.Value{})
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
iterator.values = groups
|
||||
}
|
||||
|
||||
return iterator.values.HasNext()
|
||||
}
|
||||
|
||||
func (iterator *GroupIterator) Next() (core.Value, core.Value, error) {
|
||||
return iterator.values.Next()
|
||||
}
|
||||
|
||||
func (iterator *GroupIterator) group() (*MapIterator, error) {
|
||||
groups := make(map[string]core.Value)
|
||||
|
||||
for iterator.src.HasNext() {
|
||||
for _, keyFn := range iterator.keys {
|
||||
val, _, err := iterator.src.Next()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyVal, err := keyFn(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key := keyVal.String()
|
||||
|
||||
group, exists := groups[key]
|
||||
|
||||
if !exists {
|
||||
group = values.NewArray(10)
|
||||
groups[key] = group
|
||||
}
|
||||
|
||||
group.(*values.Array).Push(val)
|
||||
}
|
||||
}
|
||||
|
||||
return NewMapIterator(groups), nil
|
||||
}
|
236
pkg/runtime/collections/iterator.go
Normal file
236
pkg/runtime/collections/iterator.go
Normal file
@@ -0,0 +1,236 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
type (
|
||||
Iterator interface {
|
||||
HasNext() bool
|
||||
Next() (value core.Value, key core.Value, err error)
|
||||
}
|
||||
|
||||
Iterable interface {
|
||||
Iterate() Iterator
|
||||
}
|
||||
|
||||
IterableExpression interface {
|
||||
core.Expression
|
||||
Iterate(ctx context.Context, scope *core.Scope) (Iterator, error)
|
||||
}
|
||||
|
||||
SliceIterator struct {
|
||||
values []core.Value
|
||||
pos int
|
||||
}
|
||||
|
||||
MapIterator struct {
|
||||
values map[string]core.Value
|
||||
keys []string
|
||||
pos int
|
||||
}
|
||||
|
||||
ArrayIterator struct {
|
||||
values *values.Array
|
||||
pos int
|
||||
}
|
||||
|
||||
ObjectIterator struct {
|
||||
values *values.Object
|
||||
keys []string
|
||||
pos int
|
||||
}
|
||||
|
||||
HtmlNodeIterator struct {
|
||||
values values.HtmlNode
|
||||
pos int
|
||||
}
|
||||
)
|
||||
|
||||
func ToIterator(value core.Value) (Iterator, error) {
|
||||
switch value.Type() {
|
||||
case core.ArrayType:
|
||||
return NewArrayIterator(value.(*values.Array)), nil
|
||||
case core.ObjectType:
|
||||
return NewObjectIterator(value.(*values.Object)), nil
|
||||
case core.HtmlElementType, core.HtmlDocumentType:
|
||||
return NewHtmlNodeIterator(value.(values.HtmlNode)), nil
|
||||
default:
|
||||
return nil, core.TypeError(
|
||||
value.Type(),
|
||||
core.ArrayType,
|
||||
core.ObjectType,
|
||||
core.HtmlDocumentType,
|
||||
core.HtmlElementType,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func ToSlice(iterator Iterator) ([]core.Value, error) {
|
||||
res := make([]core.Value, 0, 10)
|
||||
|
||||
for iterator.HasNext() {
|
||||
item, _, err := iterator.Next()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func ToMap(iterator Iterator) (map[string]core.Value, error) {
|
||||
res := make(map[string]core.Value)
|
||||
|
||||
for iterator.HasNext() {
|
||||
item, key, err := iterator.Next()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res[key.String()] = item
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func ToArray(iterator Iterator) (*values.Array, error) {
|
||||
res := values.NewArray(10)
|
||||
|
||||
for iterator.HasNext() {
|
||||
item, _, err := iterator.Next()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.Push(item)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func NewSliceIterator(input []core.Value) *SliceIterator {
|
||||
return &SliceIterator{input, 0}
|
||||
}
|
||||
|
||||
func (iterator *SliceIterator) HasNext() bool {
|
||||
return len(iterator.values) > iterator.pos
|
||||
}
|
||||
|
||||
func (iterator *SliceIterator) Next() (core.Value, core.Value, error) {
|
||||
if len(iterator.values) > iterator.pos {
|
||||
idx := iterator.pos
|
||||
val := iterator.values[idx]
|
||||
iterator.pos += 1
|
||||
|
||||
return val, values.NewInt(idx), nil
|
||||
}
|
||||
|
||||
return values.None, values.None, ErrExhausted
|
||||
}
|
||||
|
||||
func NewMapIterator(input map[string]core.Value) *MapIterator {
|
||||
return &MapIterator{input, nil, 0}
|
||||
}
|
||||
|
||||
func (iterator *MapIterator) HasNext() bool {
|
||||
// lazy initialization
|
||||
if iterator.keys == nil {
|
||||
keys := make([]string, len(iterator.values))
|
||||
|
||||
i := 0
|
||||
for k := range iterator.values {
|
||||
keys[i] = k
|
||||
i += 1
|
||||
}
|
||||
|
||||
iterator.keys = keys
|
||||
}
|
||||
|
||||
return len(iterator.keys) > iterator.pos
|
||||
}
|
||||
|
||||
func (iterator *MapIterator) Next() (core.Value, core.Value, error) {
|
||||
if len(iterator.keys) > iterator.pos {
|
||||
key := iterator.keys[iterator.pos]
|
||||
val := iterator.values[key]
|
||||
iterator.pos += 1
|
||||
|
||||
return val, values.NewString(key), nil
|
||||
}
|
||||
|
||||
return values.None, values.None, ErrExhausted
|
||||
}
|
||||
|
||||
func NewArrayIterator(input *values.Array) *ArrayIterator {
|
||||
return &ArrayIterator{input, 0}
|
||||
}
|
||||
|
||||
func (iterator *ArrayIterator) HasNext() bool {
|
||||
return int(iterator.values.Length()) > iterator.pos
|
||||
}
|
||||
|
||||
func (iterator *ArrayIterator) Next() (core.Value, core.Value, error) {
|
||||
if int(iterator.values.Length()) > iterator.pos {
|
||||
idx := iterator.pos
|
||||
val := iterator.values.Get(values.NewInt(idx))
|
||||
iterator.pos += 1
|
||||
|
||||
return val, values.NewInt(idx), nil
|
||||
}
|
||||
|
||||
return values.None, values.None, ErrExhausted
|
||||
}
|
||||
|
||||
func NewObjectIterator(input *values.Object) *ObjectIterator {
|
||||
return &ObjectIterator{input, nil, 0}
|
||||
}
|
||||
|
||||
func (iterator *ObjectIterator) HasNext() bool {
|
||||
// lazy initialization
|
||||
if iterator.keys == nil {
|
||||
iterator.keys = iterator.values.Keys()
|
||||
}
|
||||
|
||||
return len(iterator.keys) > iterator.pos
|
||||
}
|
||||
|
||||
func (iterator *ObjectIterator) Next() (core.Value, core.Value, error) {
|
||||
if len(iterator.keys) > iterator.pos {
|
||||
key := iterator.keys[iterator.pos]
|
||||
val, _ := iterator.values.Get(values.NewString(key))
|
||||
iterator.pos += 1
|
||||
|
||||
return val, values.NewString(key), nil
|
||||
}
|
||||
|
||||
return values.None, values.None, ErrExhausted
|
||||
}
|
||||
|
||||
func NewHtmlNodeIterator(input values.HtmlNode) *HtmlNodeIterator {
|
||||
return &HtmlNodeIterator{input, 0}
|
||||
}
|
||||
|
||||
func (iterator *HtmlNodeIterator) HasNext() bool {
|
||||
return iterator.values.Length() > values.NewInt(iterator.pos)
|
||||
}
|
||||
|
||||
func (iterator *HtmlNodeIterator) Next() (core.Value, core.Value, error) {
|
||||
if iterator.values.Length() > values.NewInt(iterator.pos) {
|
||||
idx := iterator.pos
|
||||
val := iterator.values.GetChildNode(values.NewInt(idx))
|
||||
|
||||
iterator.pos += 1
|
||||
|
||||
return val, values.NewInt(idx), nil
|
||||
}
|
||||
|
||||
return values.None, values.None, ErrExhausted
|
||||
}
|
356
pkg/runtime/collections/iterator_test.go
Normal file
356
pkg/runtime/collections/iterator_test.go
Normal file
@@ -0,0 +1,356 @@
|
||||
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 TestSliceIterator(t *testing.T) {
|
||||
Convey("Should iterate over a slice", t, func() {
|
||||
arr := []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
iter := collections.NewSliceIterator(arr)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
pos := 0
|
||||
|
||||
for iter.HasNext() {
|
||||
item, key, err := iter.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(key.Unwrap(), ShouldEqual, pos)
|
||||
|
||||
res = append(res, item)
|
||||
|
||||
pos += 1
|
||||
}
|
||||
|
||||
So(res, ShouldHaveLength, len(arr))
|
||||
})
|
||||
|
||||
Convey("Should iterate over a slice in the same order", t, func() {
|
||||
arr := []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
iter := collections.NewSliceIterator(arr)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := iter.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
for idx := range arr {
|
||||
expected := arr[idx]
|
||||
actual := res[idx]
|
||||
|
||||
So(actual, ShouldEqual, expected)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("Should return an error when exhausted", t, func() {
|
||||
arr := []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
iter := collections.NewSliceIterator(arr)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := iter.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
item, _, err := iter.Next()
|
||||
|
||||
So(item, ShouldEqual, values.None)
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Should NOT iterate over an empty slice", t, func() {
|
||||
arr := []core.Value{}
|
||||
|
||||
iter := collections.NewSliceIterator(arr)
|
||||
|
||||
var iterated bool
|
||||
|
||||
for iter.HasNext() {
|
||||
iterated = true
|
||||
}
|
||||
|
||||
So(iterated, ShouldBeFalse)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMapIterator(t *testing.T) {
|
||||
Convey("Should iterate over a map", t, func() {
|
||||
m := map[string]core.Value{
|
||||
"one": values.NewInt(1),
|
||||
"two": values.NewInt(2),
|
||||
"three": values.NewInt(3),
|
||||
"four": values.NewInt(4),
|
||||
"five": values.NewInt(5),
|
||||
}
|
||||
|
||||
iter := collections.NewMapIterator(m)
|
||||
|
||||
res := make([]core.Value, 0, len(m))
|
||||
|
||||
for iter.HasNext() {
|
||||
item, key, err := iter.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expected, exists := m[key.String()]
|
||||
|
||||
So(exists, ShouldBeTrue)
|
||||
So(expected, ShouldEqual, item)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
So(res, ShouldHaveLength, len(m))
|
||||
})
|
||||
|
||||
Convey("Should return an error when exhausted", t, func() {
|
||||
m := map[string]core.Value{
|
||||
"one": values.NewInt(1),
|
||||
"two": values.NewInt(2),
|
||||
"three": values.NewInt(3),
|
||||
"four": values.NewInt(4),
|
||||
"five": values.NewInt(5),
|
||||
}
|
||||
|
||||
iter := collections.NewMapIterator(m)
|
||||
|
||||
res := make([]core.Value, 0, len(m))
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := iter.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
item, _, err := iter.Next()
|
||||
|
||||
So(item, ShouldEqual, values.None)
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Should NOT iterate over a empty map", t, func() {
|
||||
m := make(map[string]core.Value)
|
||||
|
||||
iter := collections.NewMapIterator(m)
|
||||
|
||||
var iterated bool
|
||||
|
||||
for iter.HasNext() {
|
||||
iterated = true
|
||||
}
|
||||
|
||||
So(iterated, ShouldBeFalse)
|
||||
})
|
||||
}
|
||||
|
||||
func TestArrayIterator(t *testing.T) {
|
||||
Convey("Should iterate over an array", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
iter := collections.NewArrayIterator(arr)
|
||||
|
||||
res := make([]core.Value, 0, arr.Length())
|
||||
|
||||
pos := 0
|
||||
|
||||
for iter.HasNext() {
|
||||
item, key, err := iter.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(key.Unwrap(), ShouldEqual, pos)
|
||||
|
||||
res = append(res, item)
|
||||
|
||||
pos += 1
|
||||
}
|
||||
|
||||
So(res, ShouldHaveLength, arr.Length())
|
||||
})
|
||||
|
||||
Convey("Should iterate over an array in the same order", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
iter := collections.NewArrayIterator(arr)
|
||||
|
||||
res := make([]core.Value, 0, arr.Length())
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := iter.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
arr.ForEach(func(expected core.Value, idx int) bool {
|
||||
actual := res[idx]
|
||||
|
||||
So(actual, ShouldEqual, expected)
|
||||
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Should return an error when exhausted", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
iter := collections.NewArrayIterator(arr)
|
||||
|
||||
res := make([]core.Value, 0, arr.Length())
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := iter.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
item, _, err := iter.Next()
|
||||
|
||||
So(item, ShouldEqual, values.None)
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Should NOT iterate over an empty array", t, func() {
|
||||
arr := values.NewArray(10)
|
||||
|
||||
iter := collections.NewArrayIterator(arr)
|
||||
|
||||
var iterated bool
|
||||
|
||||
for iter.HasNext() {
|
||||
iterated = true
|
||||
}
|
||||
|
||||
So(iterated, ShouldBeFalse)
|
||||
})
|
||||
}
|
||||
|
||||
func TestObjectIterator(t *testing.T) {
|
||||
Convey("Should iterate over a map", t, func() {
|
||||
m := values.NewObjectWith(
|
||||
values.NewObjectProperty("one", values.NewInt(1)),
|
||||
values.NewObjectProperty("two", values.NewInt(2)),
|
||||
values.NewObjectProperty("three", values.NewInt(3)),
|
||||
values.NewObjectProperty("four", values.NewInt(4)),
|
||||
values.NewObjectProperty("five", values.NewInt(5)),
|
||||
)
|
||||
|
||||
iter := collections.NewObjectIterator(m)
|
||||
|
||||
res := make([]core.Value, 0, m.Length())
|
||||
|
||||
for iter.HasNext() {
|
||||
item, key, err := iter.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expected, exists := m.Get(values.NewString(key.String()))
|
||||
|
||||
So(exists, ShouldBeTrue)
|
||||
So(expected, ShouldEqual, item)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
So(res, ShouldHaveLength, m.Length())
|
||||
})
|
||||
|
||||
Convey("Should return an error when exhausted", t, func() {
|
||||
m := values.NewObjectWith(
|
||||
values.NewObjectProperty("one", values.NewInt(1)),
|
||||
values.NewObjectProperty("two", values.NewInt(2)),
|
||||
values.NewObjectProperty("three", values.NewInt(3)),
|
||||
values.NewObjectProperty("four", values.NewInt(4)),
|
||||
values.NewObjectProperty("five", values.NewInt(5)),
|
||||
)
|
||||
|
||||
iter := collections.NewObjectIterator(m)
|
||||
|
||||
res := make([]core.Value, 0, m.Length())
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := iter.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
item, _, err := iter.Next()
|
||||
|
||||
So(item, ShouldEqual, values.None)
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Should NOT iterate over a empty map", t, func() {
|
||||
m := values.NewObject()
|
||||
|
||||
iter := collections.NewObjectIterator(m)
|
||||
|
||||
var iterated bool
|
||||
|
||||
for iter.HasNext() {
|
||||
iterated = true
|
||||
}
|
||||
|
||||
So(iterated, ShouldBeFalse)
|
||||
})
|
||||
}
|
60
pkg/runtime/collections/limit.go
Normal file
60
pkg/runtime/collections/limit.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type LimitIterator struct {
|
||||
src Iterator
|
||||
count int
|
||||
offset int
|
||||
currCount int
|
||||
}
|
||||
|
||||
func NewLimitIterator(src Iterator, count, offset int) (*LimitIterator, error) {
|
||||
if core.IsNil(src) {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "source")
|
||||
}
|
||||
|
||||
return &LimitIterator{src, count, offset, 0}, nil
|
||||
}
|
||||
|
||||
func (i *LimitIterator) HasNext() bool {
|
||||
i.verifyOffset()
|
||||
|
||||
if i.src.HasNext() == false {
|
||||
return false
|
||||
}
|
||||
|
||||
return i.counter() < i.count
|
||||
}
|
||||
|
||||
func (i *LimitIterator) Next() (core.Value, core.Value, error) {
|
||||
if i.counter() <= i.count {
|
||||
i.currCount += 1
|
||||
|
||||
return i.src.Next()
|
||||
}
|
||||
|
||||
return nil, nil, ErrExhausted
|
||||
}
|
||||
|
||||
func (i *LimitIterator) counter() int {
|
||||
return i.currCount - i.offset
|
||||
}
|
||||
|
||||
func (i *LimitIterator) verifyOffset() {
|
||||
if i.offset == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if (i.offset < i.currCount) || i.src.HasNext() == false {
|
||||
return
|
||||
}
|
||||
|
||||
for (i.offset > i.currCount) && i.src.HasNext() {
|
||||
i.currCount += 1
|
||||
i.src.Next()
|
||||
}
|
||||
}
|
178
pkg/runtime/collections/limit_test.go
Normal file
178
pkg/runtime/collections/limit_test.go
Normal file
@@ -0,0 +1,178 @@
|
||||
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 TestLimit(t *testing.T) {
|
||||
Convey("Should limit iteration", t, func() {
|
||||
arr := []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
src, err := collections.NewLimitIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
1,
|
||||
0,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for src.HasNext() {
|
||||
item, _, err := src.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
So(len(res), ShouldEqual, 1)
|
||||
})
|
||||
|
||||
Convey("Should limit iteration (2)", t, func() {
|
||||
arr := []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
src, err := collections.NewLimitIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
2,
|
||||
0,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for src.HasNext() {
|
||||
item, _, err := src.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
So(len(res), ShouldEqual, 2)
|
||||
})
|
||||
|
||||
Convey("Should limit iteration with offset", t, func() {
|
||||
arr := []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
offset := 2
|
||||
src, err := collections.NewLimitIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
2,
|
||||
offset,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for src.HasNext() {
|
||||
item, _, err := src.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
So(len(res), ShouldEqual, 2)
|
||||
|
||||
for idx, current := range res {
|
||||
expected := arr[idx+offset]
|
||||
|
||||
So(expected, ShouldEqual, current)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("Should limit iteration with offset at the end", t, func() {
|
||||
arr := []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
offset := 3
|
||||
|
||||
src, err := collections.NewLimitIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
2,
|
||||
offset,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for src.HasNext() {
|
||||
item, _, err := src.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
So(len(res), ShouldEqual, 2)
|
||||
|
||||
for idx, current := range res {
|
||||
expected := arr[idx+offset]
|
||||
|
||||
So(expected, ShouldEqual, current)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("Should limit iteration with offset with going out of bounds", t, func() {
|
||||
arr := []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
offset := 4
|
||||
|
||||
src, err := collections.NewLimitIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
2,
|
||||
offset,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for src.HasNext() {
|
||||
item, _, err := src.Next()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
So(len(res), ShouldEqual, 1)
|
||||
})
|
||||
}
|
21
pkg/runtime/collections/reducer.go
Normal file
21
pkg/runtime/collections/reducer.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
)
|
||||
|
||||
type (
|
||||
Reducer interface {
|
||||
Reduce(collection core.Value, value core.Value) (core.Value, error)
|
||||
}
|
||||
|
||||
Reducible interface {
|
||||
Reduce() Reducer
|
||||
}
|
||||
|
||||
ReducibleExpression interface {
|
||||
core.Expression
|
||||
Reduce(ctx context.Context, scope *core.Scope) (Reducer, error)
|
||||
}
|
||||
)
|
151
pkg/runtime/collections/sort.go
Normal file
151
pkg/runtime/collections/sort.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/pkg/errors"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
SortDirection int
|
||||
|
||||
Comparator func(first core.Value, second core.Value) (int, error)
|
||||
|
||||
Sorter struct {
|
||||
fn Comparator
|
||||
direction SortDirection
|
||||
}
|
||||
|
||||
SortIterator struct {
|
||||
src Iterator
|
||||
sorters []*Sorter
|
||||
ready bool
|
||||
values *SliceIterator
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
SortDirectionAsc SortDirection = 1
|
||||
SortDirectionDesc SortDirection = -1
|
||||
)
|
||||
|
||||
func SortDirectionFromString(str string) SortDirection {
|
||||
if strings.ToUpper(str) == "DESC" {
|
||||
return SortDirectionDesc
|
||||
}
|
||||
|
||||
return SortDirectionAsc
|
||||
}
|
||||
|
||||
func IsValidSortDirection(direction SortDirection) bool {
|
||||
switch direction {
|
||||
case SortDirectionAsc, SortDirectionDesc:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func NewSorter(fn Comparator, direction SortDirection) (*Sorter, error) {
|
||||
if fn == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "fn")
|
||||
}
|
||||
|
||||
if IsValidSortDirection(direction) == false {
|
||||
return nil, core.Error(core.ErrInvalidArgument, "direction")
|
||||
}
|
||||
|
||||
return &Sorter{fn, direction}, nil
|
||||
}
|
||||
|
||||
func NewSortIterator(
|
||||
src Iterator,
|
||||
comparators ...*Sorter,
|
||||
) (*SortIterator, error) {
|
||||
if core.IsNil(src) {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "source")
|
||||
}
|
||||
|
||||
if comparators == nil || len(comparators) == 0 {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "comparator")
|
||||
}
|
||||
|
||||
return &SortIterator{src, comparators, false, nil}, nil
|
||||
}
|
||||
|
||||
func (iterator *SortIterator) HasNext() bool {
|
||||
// we need to initialize the iterator
|
||||
if iterator.ready == false {
|
||||
iterator.ready = true
|
||||
values, err := iterator.sort()
|
||||
|
||||
if err != nil {
|
||||
// set to true because we do not want to initialize next time anymore
|
||||
iterator.values = NewSliceIterator(make([]core.Value, 0, 0))
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
iterator.values = values
|
||||
}
|
||||
|
||||
return iterator.values.HasNext()
|
||||
}
|
||||
|
||||
func (iterator *SortIterator) Next() (core.Value, core.Value, error) {
|
||||
return iterator.values.Next()
|
||||
}
|
||||
|
||||
func (iterator *SortIterator) sort() (*SliceIterator, error) {
|
||||
res, err := ToSlice(iterator.src)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var failure error
|
||||
|
||||
sort.SliceStable(res, func(i, j int) bool {
|
||||
// ignore next execution
|
||||
if failure != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var out bool
|
||||
|
||||
for _, comp := range iterator.sorters {
|
||||
left := res[i]
|
||||
right := res[j]
|
||||
|
||||
eq, err := comp.fn(left, right)
|
||||
|
||||
if err != nil {
|
||||
failure = err
|
||||
out = false
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
eq = eq * int(comp.direction)
|
||||
|
||||
if eq == -1 {
|
||||
out = true
|
||||
break
|
||||
}
|
||||
|
||||
if eq == 1 {
|
||||
out = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
})
|
||||
|
||||
if failure != nil {
|
||||
return nil, failure
|
||||
}
|
||||
|
||||
return NewSliceIterator(res), nil
|
||||
}
|
312
pkg/runtime/collections/sort_test.go
Normal file
312
pkg/runtime/collections/sort_test.go
Normal file
@@ -0,0 +1,312 @@
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"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 TestSort(t *testing.T) {
|
||||
Convey("Should sort asc", t, func() {
|
||||
arr := []core.Value{
|
||||
values.NewInt(5),
|
||||
values.NewInt(1),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(2),
|
||||
}
|
||||
|
||||
s, _ := collections.NewSorter(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
return first.Compare(second), nil
|
||||
},
|
||||
collections.SortDirectionAsc,
|
||||
)
|
||||
|
||||
src, err := collections.NewSortIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
s,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res, err := collections.ToSlice(src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
numbers := []int{1, 2, 3, 4, 5}
|
||||
|
||||
for idx, num := range numbers {
|
||||
So(res[idx].Unwrap(), ShouldEqual, num)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("Should sort desc", t, func() {
|
||||
arr := []core.Value{
|
||||
values.NewInt(5),
|
||||
values.NewInt(1),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(2),
|
||||
}
|
||||
|
||||
s, _ := collections.NewSorter(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
return first.Compare(second), nil
|
||||
},
|
||||
collections.SortDirectionDesc,
|
||||
)
|
||||
|
||||
src, err := collections.NewSortIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
s,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res, err := collections.ToSlice(src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
numbers := []int{5, 4, 3, 2, 1}
|
||||
|
||||
for idx, num := range numbers {
|
||||
So(res[idx].Unwrap(), ShouldEqual, num)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("Should sort asc with multiple sorters", t, func() {
|
||||
makeObj := func(one, two int) *values.Object {
|
||||
obj := values.NewObject()
|
||||
|
||||
obj.Set("one", values.NewInt(one))
|
||||
obj.Set("two", values.NewInt(two))
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
arr := []core.Value{
|
||||
makeObj(1, 2),
|
||||
makeObj(1, 1),
|
||||
makeObj(3, 1),
|
||||
makeObj(4, 2),
|
||||
makeObj(2, 1),
|
||||
makeObj(3, 2),
|
||||
makeObj(4, 1),
|
||||
makeObj(2, 2),
|
||||
}
|
||||
|
||||
s1, _ := collections.NewSorter(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
o1, _ := first.(*values.Object).Get("one")
|
||||
o2, _ := second.(*values.Object).Get("one")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
collections.SortDirectionAsc,
|
||||
)
|
||||
|
||||
s2, _ := collections.NewSorter(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
o1, _ := first.(*values.Object).Get("two")
|
||||
o2, _ := second.(*values.Object).Get("two")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
collections.SortDirectionAsc,
|
||||
)
|
||||
|
||||
src, err := collections.NewSortIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
s1,
|
||||
s2,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res, err := collections.ToSlice(src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
j, _ := json.Marshal(res)
|
||||
|
||||
So(string(j), ShouldEqual, `[{"one":1,"two":1},{"one":1,"two":2},{"one":2,"two":1},{"one":2,"two":2},{"one":3,"two":1},{"one":3,"two":2},{"one":4,"two":1},{"one":4,"two":2}]`)
|
||||
})
|
||||
|
||||
Convey("Should sort desc with multiple sorters", t, func() {
|
||||
makeObj := func(one, two int) *values.Object {
|
||||
obj := values.NewObject()
|
||||
|
||||
obj.Set("one", values.NewInt(one))
|
||||
obj.Set("two", values.NewInt(two))
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
arr := []core.Value{
|
||||
makeObj(1, 2),
|
||||
makeObj(1, 1),
|
||||
makeObj(3, 1),
|
||||
makeObj(4, 2),
|
||||
makeObj(2, 1),
|
||||
makeObj(3, 2),
|
||||
makeObj(4, 1),
|
||||
makeObj(2, 2),
|
||||
}
|
||||
|
||||
s1, _ := collections.NewSorter(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
o1, _ := first.(*values.Object).Get("one")
|
||||
o2, _ := second.(*values.Object).Get("one")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
collections.SortDirectionDesc,
|
||||
)
|
||||
|
||||
s2, _ := collections.NewSorter(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
o1, _ := first.(*values.Object).Get("two")
|
||||
o2, _ := second.(*values.Object).Get("two")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
collections.SortDirectionDesc,
|
||||
)
|
||||
|
||||
src, err := collections.NewSortIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
s1,
|
||||
s2,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res, err := collections.ToSlice(src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
j, _ := json.Marshal(res)
|
||||
|
||||
So(string(j), ShouldEqual, `[{"one":4,"two":2},{"one":4,"two":1},{"one":3,"two":2},{"one":3,"two":1},{"one":2,"two":2},{"one":2,"two":1},{"one":1,"two":2},{"one":1,"two":1}]`)
|
||||
})
|
||||
|
||||
Convey("Should sort asc and desc with multiple sorters", t, func() {
|
||||
makeObj := func(one, two int) *values.Object {
|
||||
obj := values.NewObject()
|
||||
|
||||
obj.Set("one", values.NewInt(one))
|
||||
obj.Set("two", values.NewInt(two))
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
arr := []core.Value{
|
||||
makeObj(1, 2),
|
||||
makeObj(1, 1),
|
||||
makeObj(3, 1),
|
||||
makeObj(4, 2),
|
||||
makeObj(2, 1),
|
||||
makeObj(3, 2),
|
||||
makeObj(4, 1),
|
||||
makeObj(2, 2),
|
||||
}
|
||||
|
||||
s1, _ := collections.NewSorter(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
o1, _ := first.(*values.Object).Get("one")
|
||||
o2, _ := second.(*values.Object).Get("one")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
collections.SortDirectionAsc,
|
||||
)
|
||||
|
||||
s2, _ := collections.NewSorter(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
o1, _ := first.(*values.Object).Get("two")
|
||||
o2, _ := second.(*values.Object).Get("two")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
collections.SortDirectionDesc,
|
||||
)
|
||||
|
||||
src, err := collections.NewSortIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
s1,
|
||||
s2,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res, err := collections.ToSlice(src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
j, _ := json.Marshal(res)
|
||||
|
||||
So(string(j), ShouldEqual, `[{"one":1,"two":2},{"one":1,"two":1},{"one":2,"two":2},{"one":2,"two":1},{"one":3,"two":2},{"one":3,"two":1},{"one":4,"two":2},{"one":4,"two":1}]`)
|
||||
})
|
||||
|
||||
Convey("Should sort desc and asc with multiple sorters", t, func() {
|
||||
makeObj := func(one, two int) *values.Object {
|
||||
obj := values.NewObject()
|
||||
|
||||
obj.Set("one", values.NewInt(one))
|
||||
obj.Set("two", values.NewInt(two))
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
arr := []core.Value{
|
||||
makeObj(1, 2),
|
||||
makeObj(1, 1),
|
||||
makeObj(3, 1),
|
||||
makeObj(4, 2),
|
||||
makeObj(2, 1),
|
||||
makeObj(3, 2),
|
||||
makeObj(4, 1),
|
||||
makeObj(2, 2),
|
||||
}
|
||||
|
||||
s1, _ := collections.NewSorter(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
o1, _ := first.(*values.Object).Get("one")
|
||||
o2, _ := second.(*values.Object).Get("one")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
collections.SortDirectionDesc,
|
||||
)
|
||||
|
||||
s2, _ := collections.NewSorter(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
o1, _ := first.(*values.Object).Get("two")
|
||||
o2, _ := second.(*values.Object).Get("two")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
collections.SortDirectionAsc,
|
||||
)
|
||||
|
||||
src, err := collections.NewSortIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
s1,
|
||||
s2,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res, err := collections.ToSlice(src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
j, _ := json.Marshal(res)
|
||||
|
||||
So(string(j), ShouldEqual, `[{"one":4,"two":1},{"one":4,"two":2},{"one":3,"two":1},{"one":3,"two":2},{"one":2,"two":1},{"one":2,"two":2},{"one":1,"two":1},{"one":1,"two":2}]`)
|
||||
})
|
||||
}
|
49
pkg/runtime/core/errors.go
Normal file
49
pkg/runtime/core/errors.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMissedArgument = errors.New("missed argument")
|
||||
ErrInvalidArgument = errors.New("invalid argument")
|
||||
ErrInvalidType = errors.New("invalid type")
|
||||
ErrInvalidOperation = errors.New("invalid operation")
|
||||
ErrNotFound = errors.New("not found")
|
||||
ErrNotUnique = errors.New("not unique")
|
||||
ErrTerminated = errors.New("operation is terminated")
|
||||
ErrUnexpected = errors.New("unexpected error")
|
||||
ErrNotImplemented = errors.New("not implemented")
|
||||
)
|
||||
|
||||
const typeErrorTemplate = "expected %s, but got %s"
|
||||
|
||||
func SourceError(src SourceMap, err error) error {
|
||||
return errors.Errorf("%s %s", err.Error(), src.String())
|
||||
}
|
||||
|
||||
func TypeError(actual Type, expected ...Type) error {
|
||||
if len(expected) == 0 {
|
||||
return Error(ErrInvalidType, actual.String())
|
||||
}
|
||||
|
||||
if len(expected) == 1 {
|
||||
return Error(ErrInvalidType, fmt.Sprintf(typeErrorTemplate, expected, actual))
|
||||
}
|
||||
|
||||
strs := make([]string, len(expected))
|
||||
|
||||
for idx, t := range expected {
|
||||
strs[idx] = t.String()
|
||||
}
|
||||
|
||||
expectedStr := strings.Join(strs, " or ")
|
||||
|
||||
return Error(ErrInvalidType, fmt.Sprintf(typeErrorTemplate, expectedStr, actual))
|
||||
}
|
||||
|
||||
func Error(err error, msg string) error {
|
||||
return errors.Errorf("%s: %s", err.Error(), msg)
|
||||
}
|
7
pkg/runtime/core/expression.go
Normal file
7
pkg/runtime/core/expression.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package core
|
||||
|
||||
import "context"
|
||||
|
||||
type Expression interface {
|
||||
Exec(ctx context.Context, scope *Scope) (Value, error)
|
||||
}
|
16
pkg/runtime/core/function.go
Normal file
16
pkg/runtime/core/function.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Function = func(ctx context.Context, args ...Value) (Value, error)
|
||||
|
||||
func ValidateArgs(inputs []Value, required int) error {
|
||||
if len(inputs) != required {
|
||||
return Error(ErrMissedArgument, fmt.Sprintf("expected %d, but got %d arguments", required, len(inputs)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
25
pkg/runtime/core/helpers.go
Normal file
25
pkg/runtime/core/helpers.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package core
|
||||
|
||||
import "reflect"
|
||||
|
||||
func IsNil(input interface{}) bool {
|
||||
val := reflect.ValueOf(input)
|
||||
kind := val.Kind()
|
||||
|
||||
switch kind {
|
||||
case reflect.Ptr,
|
||||
reflect.Array,
|
||||
reflect.Slice,
|
||||
reflect.Map,
|
||||
reflect.Struct,
|
||||
reflect.Func,
|
||||
reflect.Interface,
|
||||
reflect.Chan,
|
||||
reflect.UnsafePointer:
|
||||
return val.IsNil()
|
||||
case reflect.Invalid:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
20
pkg/runtime/core/result.go
Normal file
20
pkg/runtime/core/result.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package core
|
||||
|
||||
type (
|
||||
Result struct {
|
||||
err <-chan error
|
||||
data <-chan Value
|
||||
}
|
||||
)
|
||||
|
||||
func NewResult(err <-chan error, data <-chan Value) *Result {
|
||||
return &Result{err, data}
|
||||
}
|
||||
|
||||
func (r *Result) Error() <-chan error {
|
||||
return r.err
|
||||
}
|
||||
|
||||
func (r *Result) Data() <-chan Value {
|
||||
return r.data
|
||||
}
|
114
pkg/runtime/core/scope.go
Normal file
114
pkg/runtime/core/scope.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
type (
|
||||
CloseFunc func()
|
||||
|
||||
Scope struct {
|
||||
closed bool
|
||||
vars map[string]Value
|
||||
parent *Scope
|
||||
children []*Scope
|
||||
}
|
||||
)
|
||||
|
||||
func NewRootScope() (*Scope, CloseFunc) {
|
||||
scope := NewScope(nil)
|
||||
|
||||
return scope, func() {
|
||||
scope.close()
|
||||
}
|
||||
}
|
||||
|
||||
func NewScope(parent *Scope) *Scope {
|
||||
return &Scope{
|
||||
closed: false,
|
||||
vars: make(map[string]Value),
|
||||
parent: parent,
|
||||
children: make([]*Scope, 0, 5),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scope) SetVariable(name string, val Value) error {
|
||||
_, exists := s.vars[name]
|
||||
|
||||
// it already has been declared in the current scope
|
||||
if exists {
|
||||
return errors.Wrapf(ErrNotUnique, "variable is already declared '%s'", name)
|
||||
}
|
||||
|
||||
s.vars[name] = val
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Scope) HasVariable(name string) bool {
|
||||
_, exists := s.vars[name]
|
||||
|
||||
// does not exist in the current scope
|
||||
// try to find in a parent scope
|
||||
if !exists {
|
||||
if s.parent != nil {
|
||||
return s.parent.HasVariable(name)
|
||||
}
|
||||
}
|
||||
|
||||
return exists
|
||||
}
|
||||
|
||||
func (s *Scope) GetVariable(name string) (Value, error) {
|
||||
out, exists := s.vars[name]
|
||||
|
||||
// does not exist in the current scope
|
||||
// try to find in the parent scope
|
||||
if !exists {
|
||||
if s.parent != nil {
|
||||
return s.parent.GetVariable(name)
|
||||
}
|
||||
|
||||
return nil, errors.Wrapf(ErrNotFound, "variable '%s'", name)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *Scope) Fork() *Scope {
|
||||
child := NewScope(s)
|
||||
|
||||
s.children = append(s.children, child)
|
||||
|
||||
return child
|
||||
}
|
||||
|
||||
func (s *Scope) close() error {
|
||||
if s.closed {
|
||||
return errors.Wrap(ErrInvalidOperation, "scope is already closed")
|
||||
}
|
||||
|
||||
s.closed = true
|
||||
|
||||
// close all active child scopes
|
||||
for _, c := range s.children {
|
||||
c.close()
|
||||
}
|
||||
|
||||
// do clean up
|
||||
// if some of the variables implements io.Closer interface
|
||||
// we need to close them
|
||||
for _, v := range s.vars {
|
||||
closer, ok := v.(io.Closer)
|
||||
|
||||
if ok {
|
||||
closer.Close()
|
||||
}
|
||||
}
|
||||
|
||||
s.children = nil
|
||||
s.vars = nil
|
||||
|
||||
return nil
|
||||
}
|
25
pkg/runtime/core/source.go
Normal file
25
pkg/runtime/core/source.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package core
|
||||
|
||||
import "fmt"
|
||||
|
||||
type SourceMap struct {
|
||||
text string
|
||||
line int
|
||||
column int
|
||||
}
|
||||
|
||||
func NewSourceMap(text string, line, col int) SourceMap {
|
||||
return SourceMap{text, line, col}
|
||||
}
|
||||
|
||||
func (s SourceMap) Line() int {
|
||||
return s.line
|
||||
}
|
||||
|
||||
func (s SourceMap) Column() int {
|
||||
return s.column
|
||||
}
|
||||
|
||||
func (s SourceMap) String() string {
|
||||
return fmt.Sprintf("at %d:%d", s.line, s.column)
|
||||
}
|
70
pkg/runtime/core/value.go
Normal file
70
pkg/runtime/core/value.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type Type int64
|
||||
|
||||
const (
|
||||
NoneType Type = 0
|
||||
BooleanType Type = 1
|
||||
IntType Type = 2
|
||||
FloatType Type = 3
|
||||
StringType Type = 4
|
||||
DateTimeType Type = 5
|
||||
ArrayType Type = 6
|
||||
ObjectType Type = 7
|
||||
HtmlElementType Type = 8
|
||||
HtmlDocumentType Type = 9
|
||||
BinaryType Type = 10
|
||||
)
|
||||
|
||||
var typestr = map[Type]string{
|
||||
NoneType: "none",
|
||||
BooleanType: "boolean",
|
||||
IntType: "int",
|
||||
FloatType: "float",
|
||||
StringType: "string",
|
||||
DateTimeType: "datetime",
|
||||
ArrayType: "array",
|
||||
ObjectType: "object",
|
||||
HtmlElementType: "HTMLElement",
|
||||
HtmlDocumentType: "HTMLDocument",
|
||||
BinaryType: "BinaryType",
|
||||
}
|
||||
|
||||
func (t Type) String() string {
|
||||
return typestr[t]
|
||||
}
|
||||
|
||||
type Value interface {
|
||||
json.Marshaler
|
||||
Type() Type
|
||||
String() string
|
||||
Compare(other Value) int
|
||||
Unwrap() interface{}
|
||||
Hash() int
|
||||
}
|
||||
|
||||
func IsTypeOf(value Value, check Type) bool {
|
||||
return value.Type() == check
|
||||
}
|
||||
|
||||
func ValidateType(value Value, required ...Type) error {
|
||||
var valid bool
|
||||
ct := value.Type()
|
||||
|
||||
for _, t := range required {
|
||||
if ct == t {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !valid {
|
||||
return TypeError(value.Type(), required...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
59
pkg/runtime/expressions/block.go
Normal file
59
pkg/runtime/expressions/block.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package expressions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type BlockExpression struct {
|
||||
statements []core.Expression
|
||||
expression core.Expression
|
||||
}
|
||||
|
||||
func NewBlockExpression(size int) *BlockExpression {
|
||||
return &BlockExpression{make([]core.Expression, 0, size), nil}
|
||||
}
|
||||
|
||||
func NewBlockExpressionWith(elements ...core.Expression) *BlockExpression {
|
||||
block := NewBlockExpression(len(elements))
|
||||
|
||||
for _, el := range elements {
|
||||
block.Add(el)
|
||||
}
|
||||
|
||||
return block
|
||||
}
|
||||
|
||||
func (b *BlockExpression) Add(exp core.Expression) error {
|
||||
switch exp.(type) {
|
||||
case *ForExpression, *ReturnExpression:
|
||||
// return an error?
|
||||
if !core.IsNil(b.expression) {
|
||||
return errors.Wrap(core.ErrInvalidOperation, "return expression is already defined")
|
||||
}
|
||||
|
||||
b.expression = exp
|
||||
|
||||
break
|
||||
default:
|
||||
b.statements = append(b.statements, exp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BlockExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
for _, exp := range b.statements {
|
||||
if _, err := exp.Exec(ctx, scope); err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
}
|
||||
|
||||
if !core.IsNil(b.expression) {
|
||||
return b.expression.Exec(ctx, scope)
|
||||
}
|
||||
|
||||
return values.None, nil
|
||||
}
|
33
pkg/runtime/expressions/clauses/clause.go
Normal file
33
pkg/runtime/expressions/clauses/clause.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package clauses
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
type baseClause struct {
|
||||
src core.SourceMap
|
||||
dataSource collections.IterableExpression
|
||||
}
|
||||
|
||||
func (clause *baseClause) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||
src, err := clause.dataSource.Iterate(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return src, nil
|
||||
}
|
||||
|
||||
func (clause *baseClause) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
iterator, err := clause.Iterate(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
return collections.ToArray(iterator)
|
||||
}
|
60
pkg/runtime/expressions/clauses/filter.go
Normal file
60
pkg/runtime/expressions/clauses/filter.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package clauses
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
type FilterClause struct {
|
||||
*baseClause
|
||||
valVar string
|
||||
keyVar string
|
||||
predicate core.Expression
|
||||
}
|
||||
|
||||
func NewFilterClause(
|
||||
src core.SourceMap,
|
||||
dataSource collections.IterableExpression,
|
||||
valVar string,
|
||||
keyVar string,
|
||||
predicate core.Expression,
|
||||
) *FilterClause {
|
||||
return &FilterClause{
|
||||
&baseClause{src, dataSource},
|
||||
valVar,
|
||||
keyVar,
|
||||
predicate,
|
||||
}
|
||||
}
|
||||
|
||||
func (clause *FilterClause) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||
src, err := clause.dataSource.Iterate(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return collections.NewFilterIterator(src, func(val core.Value, key core.Value) (bool, error) {
|
||||
innerScope := scope.Fork()
|
||||
|
||||
innerScope.SetVariable(clause.valVar, val)
|
||||
|
||||
if clause.keyVar != "" {
|
||||
innerScope.SetVariable(clause.keyVar, key)
|
||||
}
|
||||
|
||||
ret, err := clause.predicate.Exec(ctx, innerScope)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if ret == values.True {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
})
|
||||
}
|
32
pkg/runtime/expressions/clauses/limit.go
Normal file
32
pkg/runtime/expressions/clauses/limit.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package clauses
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
)
|
||||
|
||||
type LimitClause struct {
|
||||
*baseClause
|
||||
count int
|
||||
offset int
|
||||
}
|
||||
|
||||
func NewLimitClause(
|
||||
src core.SourceMap,
|
||||
dataSource collections.IterableExpression,
|
||||
count int,
|
||||
offset int,
|
||||
) *LimitClause {
|
||||
return &LimitClause{&baseClause{src, dataSource}, count, offset}
|
||||
}
|
||||
|
||||
func (clause *LimitClause) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||
src, err := clause.dataSource.Iterate(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return collections.NewLimitIterator(src, clause.count, clause.offset)
|
||||
}
|
83
pkg/runtime/expressions/clauses/sort.go
Normal file
83
pkg/runtime/expressions/clauses/sort.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package clauses
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
)
|
||||
|
||||
type (
|
||||
SorterExpression struct {
|
||||
expression core.Expression
|
||||
direction collections.SortDirection
|
||||
}
|
||||
SortClause struct {
|
||||
*baseClause
|
||||
variableName string
|
||||
sorters []*SorterExpression
|
||||
}
|
||||
)
|
||||
|
||||
func NewSorterExpression(expression core.Expression, direction collections.SortDirection) (*SorterExpression, error) {
|
||||
if expression == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "expression")
|
||||
}
|
||||
|
||||
if !collections.IsValidSortDirection(direction) {
|
||||
return nil, core.Error(core.ErrInvalidArgument, "direction")
|
||||
}
|
||||
|
||||
return &SorterExpression{expression, direction}, nil
|
||||
}
|
||||
|
||||
func NewSortClause(
|
||||
src core.SourceMap,
|
||||
dataSource collections.IterableExpression,
|
||||
variableName string,
|
||||
sorters ...*SorterExpression,
|
||||
) *SortClause {
|
||||
return &SortClause{&baseClause{src, dataSource}, variableName, sorters}
|
||||
}
|
||||
|
||||
func (clause *SortClause) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||
src, err := clause.dataSource.Iterate(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sorters := make([]*collections.Sorter, len(clause.sorters))
|
||||
|
||||
// converting sorter expression into collections.Sorter
|
||||
for idx, srt := range clause.sorters {
|
||||
sorter, err := collections.NewSorter(func(first core.Value, second core.Value) (int, error) {
|
||||
scope1 := scope.Fork()
|
||||
scope1.SetVariable(clause.variableName, first)
|
||||
|
||||
f, err := srt.expression.Exec(ctx, scope1)
|
||||
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
scope2 := scope.Fork()
|
||||
scope2.SetVariable(clause.variableName, second)
|
||||
|
||||
s, err := srt.expression.Exec(ctx, scope2)
|
||||
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return f.Compare(s), nil
|
||||
}, srt.direction)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sorters[idx] = sorter
|
||||
}
|
||||
|
||||
return collections.NewSortIterator(src, sorters...)
|
||||
}
|
134
pkg/runtime/expressions/for.go
Normal file
134
pkg/runtime/expressions/for.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package expressions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/expressions/clauses"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ForExpression struct {
|
||||
src core.SourceMap
|
||||
valVar string
|
||||
keyVar string
|
||||
dataSource collections.IterableExpression
|
||||
predicate core.Expression
|
||||
distinct bool
|
||||
spread bool
|
||||
}
|
||||
|
||||
func NewForExpression(
|
||||
src core.SourceMap,
|
||||
valVar string,
|
||||
keyVar string,
|
||||
dataSource collections.IterableExpression,
|
||||
predicate core.Expression,
|
||||
distinct bool,
|
||||
spread bool,
|
||||
) (*ForExpression, error) {
|
||||
if valVar == "" {
|
||||
return nil, errors.Wrap(core.ErrInvalidArgument, "valVar is empty")
|
||||
}
|
||||
|
||||
if core.IsNil(dataSource) {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "missed source expression")
|
||||
}
|
||||
|
||||
if core.IsNil(predicate) {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "missed return expression")
|
||||
}
|
||||
|
||||
return &ForExpression{
|
||||
src,
|
||||
valVar, keyVar,
|
||||
dataSource,
|
||||
predicate,
|
||||
distinct,
|
||||
spread,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *ForExpression) AddLimit(src core.SourceMap, size, count int) {
|
||||
e.dataSource = clauses.NewLimitClause(src, e.dataSource, size, count)
|
||||
}
|
||||
|
||||
func (e *ForExpression) AddFilter(src core.SourceMap, exp core.Expression) {
|
||||
e.dataSource = clauses.NewFilterClause(src, e.dataSource, e.valVar, e.keyVar, exp)
|
||||
}
|
||||
|
||||
func (e *ForExpression) AddSort(src core.SourceMap, sorters ...*clauses.SorterExpression) {
|
||||
e.dataSource = clauses.NewSortClause(src, e.dataSource, e.valVar, sorters...)
|
||||
}
|
||||
|
||||
func (e *ForExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
iterator, err := e.dataSource.Iterate(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
// Hash map for a check for uniqueness
|
||||
var hashes map[int]bool
|
||||
|
||||
if e.distinct {
|
||||
hashes = make(map[int]bool)
|
||||
}
|
||||
|
||||
res := values.NewArray(10)
|
||||
|
||||
for iterator.HasNext() {
|
||||
val, key, err := iterator.Next()
|
||||
|
||||
if err != nil {
|
||||
return values.None, core.SourceError(e.src, err)
|
||||
}
|
||||
|
||||
innerScope := scope.Fork()
|
||||
innerScope.SetVariable(e.valVar, val)
|
||||
|
||||
if e.keyVar != "" {
|
||||
innerScope.SetVariable(e.keyVar, key)
|
||||
}
|
||||
|
||||
out, err := e.predicate.Exec(ctx, innerScope)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
var el core.Value
|
||||
|
||||
// The result shouldn't be distinct
|
||||
// Just add the output
|
||||
if !e.distinct {
|
||||
el = out
|
||||
} else {
|
||||
// We need to check whether the value already exists in the result set
|
||||
hash := out.Hash()
|
||||
_, exists := hashes[hash]
|
||||
|
||||
if !exists {
|
||||
hashes[hash] = true
|
||||
el = out
|
||||
}
|
||||
}
|
||||
|
||||
if el != nil {
|
||||
if !e.spread {
|
||||
res.Push(el)
|
||||
} else {
|
||||
elements := el.(*values.Array)
|
||||
|
||||
elements.ForEach(func(i core.Value, _ int) bool {
|
||||
res.Push(i)
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
62
pkg/runtime/expressions/func_call.go
Normal file
62
pkg/runtime/expressions/func_call.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package expressions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
type FunctionCallExpression struct {
|
||||
src core.SourceMap
|
||||
fun core.Function
|
||||
args []core.Expression
|
||||
}
|
||||
|
||||
func NewFunctionCallExpression(
|
||||
src core.SourceMap,
|
||||
fun core.Function,
|
||||
args ...core.Expression,
|
||||
) (*FunctionCallExpression, error) {
|
||||
if fun == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "function")
|
||||
}
|
||||
|
||||
return &FunctionCallExpression{src, fun, args}, nil
|
||||
}
|
||||
|
||||
func (e *FunctionCallExpression) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||
value, err := e.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, core.SourceError(e.src, err)
|
||||
}
|
||||
|
||||
iter, err := collections.ToIterator(value)
|
||||
|
||||
if err != nil {
|
||||
return nil, core.SourceError(e.src, err)
|
||||
}
|
||||
|
||||
return iter, nil
|
||||
}
|
||||
|
||||
func (e *FunctionCallExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
if len(e.args) == 0 {
|
||||
return e.fun(ctx)
|
||||
}
|
||||
|
||||
args := make([]core.Value, len(e.args))
|
||||
|
||||
for idx, arg := range e.args {
|
||||
out, err := arg.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return values.None, core.SourceError(e.src, err)
|
||||
}
|
||||
|
||||
args[idx] = out
|
||||
}
|
||||
|
||||
return e.fun(ctx, args...)
|
||||
}
|
60
pkg/runtime/expressions/literals/array.go
Normal file
60
pkg/runtime/expressions/literals/array.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package literals
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
type ArrayLiteral struct {
|
||||
elements []core.Expression
|
||||
}
|
||||
|
||||
func NewArrayLiteral(size int) *ArrayLiteral {
|
||||
return &ArrayLiteral{make([]core.Expression, 0, size)}
|
||||
}
|
||||
|
||||
func NewArrayLiteralWith(elements ...core.Expression) *ArrayLiteral {
|
||||
return &ArrayLiteral{elements}
|
||||
}
|
||||
|
||||
func (l *ArrayLiteral) Push(expression core.Expression) {
|
||||
l.elements = append(l.elements, expression)
|
||||
}
|
||||
|
||||
func (l *ArrayLiteral) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||
arr, err := l.doExec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return collections.NewArrayIterator(arr), nil
|
||||
}
|
||||
|
||||
func (l *ArrayLiteral) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
arr, err := l.doExec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
return arr, nil
|
||||
}
|
||||
|
||||
func (l *ArrayLiteral) doExec(ctx context.Context, scope *core.Scope) (*values.Array, error) {
|
||||
arr := values.NewArray(len(l.elements))
|
||||
|
||||
for _, el := range l.elements {
|
||||
val, err := el.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
arr.Push(val)
|
||||
}
|
||||
|
||||
return arr, nil
|
||||
}
|
21
pkg/runtime/expressions/literals/boolean.go
Normal file
21
pkg/runtime/expressions/literals/boolean.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package literals
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
type BooleanLiteral bool
|
||||
|
||||
func NewBooleanLiteral(val bool) BooleanLiteral {
|
||||
return BooleanLiteral(val)
|
||||
}
|
||||
|
||||
func (l BooleanLiteral) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
if l == true {
|
||||
return values.True, nil
|
||||
}
|
||||
|
||||
return values.False, nil
|
||||
}
|
17
pkg/runtime/expressions/literals/float.go
Normal file
17
pkg/runtime/expressions/literals/float.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package literals
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
type FloatLiteral float64
|
||||
|
||||
func NewFloatLiteral(value float64) FloatLiteral {
|
||||
return FloatLiteral(value)
|
||||
}
|
||||
|
||||
func (l FloatLiteral) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
return values.NewFloat(float64(l)), nil
|
||||
}
|
17
pkg/runtime/expressions/literals/int.go
Normal file
17
pkg/runtime/expressions/literals/int.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package literals
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
type IntLiteral int
|
||||
|
||||
func NewIntLiteral(value int) IntLiteral {
|
||||
return IntLiteral(value)
|
||||
}
|
||||
|
||||
func (l IntLiteral) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
return values.NewInt(int(l)), nil
|
||||
}
|
15
pkg/runtime/expressions/literals/none.go
Normal file
15
pkg/runtime/expressions/literals/none.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package literals
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
type noneLiteral struct{}
|
||||
|
||||
var None = &noneLiteral{}
|
||||
|
||||
func (l noneLiteral) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
return values.None, nil
|
||||
}
|
77
pkg/runtime/expressions/literals/object.go
Normal file
77
pkg/runtime/expressions/literals/object.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package literals
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
type (
|
||||
ObjectPropertyAssignment struct {
|
||||
name core.Expression
|
||||
value core.Expression
|
||||
}
|
||||
|
||||
ObjectLiteral struct {
|
||||
properties []*ObjectPropertyAssignment
|
||||
}
|
||||
)
|
||||
|
||||
func NewObjectPropertyAssignment(name, value core.Expression) *ObjectPropertyAssignment {
|
||||
return &ObjectPropertyAssignment{name, value}
|
||||
}
|
||||
|
||||
func NewObjectLiteral() *ObjectLiteral {
|
||||
return &ObjectLiteral{make([]*ObjectPropertyAssignment, 0, 10)}
|
||||
}
|
||||
|
||||
func NewObjectLiteralWith(props ...*ObjectPropertyAssignment) *ObjectLiteral {
|
||||
return &ObjectLiteral{props}
|
||||
}
|
||||
|
||||
func (l *ObjectLiteral) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||
obj, err := l.doExec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return collections.NewObjectIterator(obj), nil
|
||||
}
|
||||
|
||||
func (l *ObjectLiteral) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
arr, err := l.doExec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
return arr, nil
|
||||
}
|
||||
|
||||
func (l *ObjectLiteral) doExec(ctx context.Context, scope *core.Scope) (*values.Object, error) {
|
||||
obj := values.NewObject()
|
||||
|
||||
for _, el := range l.properties {
|
||||
name, err := el.name.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
val, err := el.value.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if name.Type() != core.StringType {
|
||||
return nil, core.TypeError(name.Type(), core.StringType)
|
||||
}
|
||||
|
||||
obj.Set(name.(values.String), val)
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
17
pkg/runtime/expressions/literals/string.go
Normal file
17
pkg/runtime/expressions/literals/string.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package literals
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
type StringLiteral string
|
||||
|
||||
func NewStringLiteral(str string) StringLiteral {
|
||||
return StringLiteral(str)
|
||||
}
|
||||
|
||||
func (l StringLiteral) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
return values.NewString(string(l)), nil
|
||||
}
|
74
pkg/runtime/expressions/member.go
Normal file
74
pkg/runtime/expressions/member.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package expressions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type MemberExpression struct {
|
||||
src core.SourceMap
|
||||
variableName string
|
||||
path []core.Expression
|
||||
}
|
||||
|
||||
func NewMemberExpression(src core.SourceMap, variableName string, path []core.Expression) (*MemberExpression, error) {
|
||||
if variableName == "" {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "variable name")
|
||||
}
|
||||
|
||||
if path == nil || len(path) == 0 {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "path expressions")
|
||||
}
|
||||
|
||||
return &MemberExpression{src, variableName, path}, nil
|
||||
}
|
||||
|
||||
func (e *MemberExpression) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||
value, err := e.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, core.SourceError(e.src, err)
|
||||
}
|
||||
|
||||
iter, err := collections.ToIterator(value)
|
||||
|
||||
if err != nil {
|
||||
return nil, core.SourceError(e.src, err)
|
||||
}
|
||||
|
||||
return iter, nil
|
||||
}
|
||||
|
||||
func (e *MemberExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
val, err := scope.GetVariable(e.variableName)
|
||||
|
||||
if err != nil {
|
||||
return values.None, core.SourceError(
|
||||
e.src,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
strPath := make([]core.Value, len(e.path))
|
||||
|
||||
for idx, exp := range e.path {
|
||||
segment, err := exp.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
strPath[idx] = segment
|
||||
}
|
||||
|
||||
out, err := values.GetIn(val, strPath)
|
||||
|
||||
if err != nil {
|
||||
return values.None, core.SourceError(e.src, err)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
56
pkg/runtime/expressions/operators/equality.go
Normal file
56
pkg/runtime/expressions/operators/equality.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package operators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
)
|
||||
|
||||
type (
|
||||
EqualityOperator struct {
|
||||
*baseOperator
|
||||
fn Operator
|
||||
}
|
||||
)
|
||||
|
||||
var equalityOperators = map[string]Operator{
|
||||
"==": Equal,
|
||||
"!=": NotEqual,
|
||||
">": Greater,
|
||||
"<": Less,
|
||||
">=": GreaterOrEqual,
|
||||
"<=": LessOrEqual,
|
||||
}
|
||||
|
||||
func NewEqualityOperator(
|
||||
src core.SourceMap,
|
||||
left core.Expression,
|
||||
right core.Expression,
|
||||
operator string,
|
||||
) (*EqualityOperator, error) {
|
||||
fn, exists := equalityOperators[operator]
|
||||
|
||||
if !exists {
|
||||
return nil, core.Error(core.ErrInvalidArgument, "operator")
|
||||
}
|
||||
|
||||
return &EqualityOperator{
|
||||
&baseOperator{src, left, right},
|
||||
fn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (operator *EqualityOperator) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
left, err := operator.left.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
right, err := operator.right.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return operator.fn(left, right), nil
|
||||
}
|
93
pkg/runtime/expressions/operators/logical.go
Normal file
93
pkg/runtime/expressions/operators/logical.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package operators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
type (
|
||||
LogicalOperatorType int
|
||||
LogicalOperator struct {
|
||||
*baseOperator
|
||||
value LogicalOperatorType
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
AndType LogicalOperatorType = 0
|
||||
OrType LogicalOperatorType = 1
|
||||
NotType LogicalOperatorType = 2
|
||||
)
|
||||
|
||||
var logicalOperators = map[string]LogicalOperatorType{
|
||||
"&&": AndType,
|
||||
"AND": AndType,
|
||||
"||": OrType,
|
||||
"OR": OrType,
|
||||
"NOT": NotType,
|
||||
}
|
||||
|
||||
func NewLogicalOperator(
|
||||
src core.SourceMap,
|
||||
left core.Expression,
|
||||
right core.Expression,
|
||||
operator string,
|
||||
) (*LogicalOperator, error) {
|
||||
op, exists := logicalOperators[operator]
|
||||
|
||||
if !exists {
|
||||
return nil, core.Error(core.ErrInvalidArgument, "value")
|
||||
}
|
||||
|
||||
return &LogicalOperator{
|
||||
&baseOperator{
|
||||
src,
|
||||
left,
|
||||
right,
|
||||
},
|
||||
op,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (operator *LogicalOperator) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
left, err := operator.left.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
left = operator.ensureType(left)
|
||||
|
||||
if operator.value == NotType {
|
||||
return Not(left, values.None), nil
|
||||
}
|
||||
|
||||
if operator.value == AndType && left == values.False {
|
||||
return values.False, nil
|
||||
}
|
||||
|
||||
if operator.value == OrType && left == values.True {
|
||||
return values.True, nil
|
||||
}
|
||||
|
||||
right, err := operator.right.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return operator.ensureType(right), nil
|
||||
}
|
||||
|
||||
func (operator *LogicalOperator) ensureType(value core.Value) core.Value {
|
||||
if value.Type() != core.BooleanType {
|
||||
if value.Type() == core.NoneType {
|
||||
return values.False
|
||||
}
|
||||
|
||||
return values.True
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
68
pkg/runtime/expressions/operators/math.go
Normal file
68
pkg/runtime/expressions/operators/math.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package operators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
type MathOperator struct {
|
||||
*baseOperator
|
||||
fn Operator
|
||||
leftOnly bool
|
||||
}
|
||||
|
||||
var mathOperators = map[string]Operator{
|
||||
"+": Add,
|
||||
"-": Subtract,
|
||||
"*": Multiply,
|
||||
"/": Divide,
|
||||
"%": Modulus,
|
||||
"++": Increment,
|
||||
"--": Decrement,
|
||||
}
|
||||
|
||||
func NewMathOperator(
|
||||
src core.SourceMap,
|
||||
left core.Expression,
|
||||
right core.Expression,
|
||||
operator string,
|
||||
) (*MathOperator, error) {
|
||||
fn, exists := mathOperators[operator]
|
||||
|
||||
if !exists {
|
||||
return nil, core.Error(core.ErrInvalidArgument, "operator")
|
||||
}
|
||||
|
||||
var leftOnly bool
|
||||
|
||||
if operator == "++" || operator == "--" {
|
||||
leftOnly = true
|
||||
}
|
||||
|
||||
return &MathOperator{
|
||||
&baseOperator{src, left, right},
|
||||
fn,
|
||||
leftOnly,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (operator *MathOperator) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
left, err := operator.left.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if operator.leftOnly {
|
||||
return operator.fn(left, values.None), nil
|
||||
}
|
||||
|
||||
right, err := operator.right.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return operator.fn(left, right), nil
|
||||
}
|
296
pkg/runtime/expressions/operators/operator.go
Normal file
296
pkg/runtime/expressions/operators/operator.go
Normal file
@@ -0,0 +1,296 @@
|
||||
package operators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
type Operator func(left, right core.Value) core.Value
|
||||
|
||||
type baseOperator struct {
|
||||
src core.SourceMap
|
||||
left core.Expression
|
||||
right core.Expression
|
||||
}
|
||||
|
||||
func (operator *baseOperator) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
return values.None, core.ErrInvalidOperation
|
||||
}
|
||||
|
||||
// Equality
|
||||
func Equal(left, right core.Value) core.Value {
|
||||
if left.Compare(right) == 0 {
|
||||
return values.True
|
||||
}
|
||||
|
||||
return values.False
|
||||
}
|
||||
|
||||
func NotEqual(left, right core.Value) core.Value {
|
||||
if left.Compare(right) != 0 {
|
||||
return values.True
|
||||
}
|
||||
|
||||
return values.False
|
||||
}
|
||||
|
||||
func Less(left, right core.Value) core.Value {
|
||||
if left.Compare(right) < 0 {
|
||||
return values.True
|
||||
}
|
||||
|
||||
return values.False
|
||||
}
|
||||
|
||||
func LessOrEqual(left, right core.Value) core.Value {
|
||||
out := left.Compare(right)
|
||||
|
||||
if out < 0 || out == 0 {
|
||||
return values.True
|
||||
}
|
||||
|
||||
return values.False
|
||||
}
|
||||
|
||||
func Greater(left, right core.Value) core.Value {
|
||||
if left.Compare(right) > 0 {
|
||||
return values.True
|
||||
}
|
||||
|
||||
return values.False
|
||||
}
|
||||
|
||||
func GreaterOrEqual(left, right core.Value) core.Value {
|
||||
out := left.Compare(right)
|
||||
|
||||
if out > 0 || out == 0 {
|
||||
return values.True
|
||||
}
|
||||
|
||||
return values.False
|
||||
}
|
||||
|
||||
func Not(left, _ core.Value) core.Value {
|
||||
if left == values.True {
|
||||
return values.False
|
||||
} else if left == values.False {
|
||||
return values.True
|
||||
}
|
||||
|
||||
return values.False
|
||||
}
|
||||
|
||||
// Adds numbers
|
||||
// Concats strings
|
||||
func Add(left, right core.Value) core.Value {
|
||||
if left.Type() == core.IntType {
|
||||
if right.Type() == core.IntType {
|
||||
l := left.Unwrap().(int)
|
||||
r := right.Unwrap().(int)
|
||||
|
||||
return values.NewInt(l + r)
|
||||
}
|
||||
|
||||
if right.Type() == core.FloatType {
|
||||
l := left.Unwrap().(int)
|
||||
r := right.Unwrap().(float64)
|
||||
|
||||
return values.Float(float64(l) + r)
|
||||
}
|
||||
}
|
||||
|
||||
if left.Type() == core.FloatType {
|
||||
if right.Type() == core.FloatType {
|
||||
l := left.Unwrap().(float64)
|
||||
r := right.Unwrap().(float64)
|
||||
|
||||
return values.Float(l + r)
|
||||
}
|
||||
|
||||
if right.Type() == core.IntType {
|
||||
l := left.Unwrap().(float64)
|
||||
r := right.Unwrap().(int)
|
||||
|
||||
return values.Float(l + float64(r))
|
||||
}
|
||||
}
|
||||
|
||||
return values.NewString(left.String() + right.String())
|
||||
}
|
||||
|
||||
func Subtract(left, right core.Value) core.Value {
|
||||
if left.Type() == core.IntType {
|
||||
if right.Type() == core.IntType {
|
||||
l := left.Unwrap().(int)
|
||||
r := right.Unwrap().(int)
|
||||
|
||||
return values.NewInt(l - r)
|
||||
}
|
||||
|
||||
if right.Type() == core.FloatType {
|
||||
l := left.Unwrap().(int)
|
||||
r := right.Unwrap().(float64)
|
||||
|
||||
return values.Float(float64(l) - r)
|
||||
}
|
||||
}
|
||||
|
||||
if left.Type() == core.FloatType {
|
||||
if right.Type() == core.FloatType {
|
||||
l := left.Unwrap().(float64)
|
||||
r := right.Unwrap().(float64)
|
||||
|
||||
return values.Float(l - r)
|
||||
}
|
||||
|
||||
if right.Type() == core.IntType {
|
||||
l := left.Unwrap().(float64)
|
||||
r := right.Unwrap().(int)
|
||||
|
||||
return values.Float(l - float64(r))
|
||||
}
|
||||
}
|
||||
|
||||
return values.ZeroInt
|
||||
}
|
||||
|
||||
func Multiply(left, right core.Value) core.Value {
|
||||
if left.Type() == core.IntType {
|
||||
if right.Type() == core.IntType {
|
||||
l := left.Unwrap().(int)
|
||||
r := right.Unwrap().(int)
|
||||
|
||||
return values.NewInt(l * r)
|
||||
}
|
||||
|
||||
if right.Type() == core.FloatType {
|
||||
l := left.Unwrap().(int)
|
||||
r := right.Unwrap().(float64)
|
||||
|
||||
return values.Float(float64(l) * r)
|
||||
}
|
||||
}
|
||||
|
||||
if left.Type() == core.FloatType {
|
||||
if right.Type() == core.FloatType {
|
||||
l := left.Unwrap().(float64)
|
||||
r := right.Unwrap().(float64)
|
||||
|
||||
return values.Float(l * r)
|
||||
}
|
||||
|
||||
if right.Type() == core.IntType {
|
||||
l := left.Unwrap().(float64)
|
||||
r := right.Unwrap().(int)
|
||||
|
||||
return values.Float(l * float64(r))
|
||||
}
|
||||
}
|
||||
|
||||
return values.ZeroInt
|
||||
}
|
||||
|
||||
func Divide(left, right core.Value) core.Value {
|
||||
if left.Type() == core.IntType {
|
||||
if right.Type() == core.IntType {
|
||||
l := left.Unwrap().(int)
|
||||
r := right.Unwrap().(int)
|
||||
|
||||
return values.NewInt(l / r)
|
||||
}
|
||||
|
||||
if right.Type() == core.FloatType {
|
||||
l := left.Unwrap().(int)
|
||||
r := right.Unwrap().(float64)
|
||||
|
||||
return values.Float(float64(l) / r)
|
||||
}
|
||||
}
|
||||
|
||||
if left.Type() == core.FloatType {
|
||||
if right.Type() == core.FloatType {
|
||||
l := left.Unwrap().(float64)
|
||||
r := right.Unwrap().(float64)
|
||||
|
||||
return values.Float(l / r)
|
||||
}
|
||||
|
||||
if right.Type() == core.IntType {
|
||||
l := left.Unwrap().(float64)
|
||||
r := right.Unwrap().(int)
|
||||
|
||||
return values.Float(l / float64(r))
|
||||
}
|
||||
}
|
||||
|
||||
return values.ZeroInt
|
||||
}
|
||||
|
||||
func Modulus(left, right core.Value) core.Value {
|
||||
if left.Type() == core.IntType {
|
||||
if right.Type() == core.IntType {
|
||||
l := left.Unwrap().(int)
|
||||
r := right.Unwrap().(int)
|
||||
|
||||
return values.NewInt(l % r)
|
||||
}
|
||||
|
||||
if right.Type() == core.FloatType {
|
||||
l := left.Unwrap().(int)
|
||||
r := right.Unwrap().(float64)
|
||||
|
||||
return values.Float(l % int(r))
|
||||
}
|
||||
}
|
||||
|
||||
if left.Type() == core.FloatType {
|
||||
if right.Type() == core.FloatType {
|
||||
l := left.Unwrap().(float64)
|
||||
r := right.Unwrap().(float64)
|
||||
|
||||
return values.Float(int(l) % int(r))
|
||||
}
|
||||
|
||||
if right.Type() == core.IntType {
|
||||
l := left.Unwrap().(float64)
|
||||
r := right.Unwrap().(int)
|
||||
|
||||
return values.Float(int(l) % r)
|
||||
}
|
||||
}
|
||||
|
||||
return values.ZeroInt
|
||||
}
|
||||
|
||||
func Increment(left, _ core.Value) core.Value {
|
||||
if left.Type() == core.IntType {
|
||||
l := left.Unwrap().(int)
|
||||
|
||||
return values.NewInt(l + 1)
|
||||
}
|
||||
|
||||
if left.Type() == core.FloatType {
|
||||
l := left.Unwrap().(float64)
|
||||
|
||||
return values.Float(l + 1)
|
||||
}
|
||||
|
||||
return values.None
|
||||
}
|
||||
|
||||
func Decrement(left, _ core.Value) core.Value {
|
||||
if left.Type() == core.IntType {
|
||||
l := left.Unwrap().(int)
|
||||
|
||||
return values.NewInt(l - 1)
|
||||
}
|
||||
|
||||
if left.Type() == core.FloatType {
|
||||
l := left.Unwrap().(float64)
|
||||
|
||||
return values.Float(l - 1)
|
||||
}
|
||||
|
||||
return values.None
|
||||
}
|
39
pkg/runtime/expressions/return.go
Normal file
39
pkg/runtime/expressions/return.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package expressions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
ReturnExpression struct {
|
||||
src core.SourceMap
|
||||
predicate core.Expression
|
||||
}
|
||||
)
|
||||
|
||||
func NewReturnExpression(
|
||||
src core.SourceMap,
|
||||
predicate core.Expression,
|
||||
) (*ReturnExpression, error) {
|
||||
if predicate == nil {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "expression")
|
||||
}
|
||||
|
||||
return &ReturnExpression{
|
||||
src,
|
||||
predicate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *ReturnExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
val, err := e.predicate.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return values.None, core.SourceError(e.src, err)
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
73
pkg/runtime/expressions/variable.go
Normal file
73
pkg/runtime/expressions/variable.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package expressions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
VariableExpression struct {
|
||||
src core.SourceMap
|
||||
name string
|
||||
}
|
||||
|
||||
VariableDeclarationExpression struct {
|
||||
*VariableExpression
|
||||
init core.Expression
|
||||
}
|
||||
)
|
||||
|
||||
func NewVariableExpression(src core.SourceMap, name string) (*VariableExpression, error) {
|
||||
if name == "" {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "missed variable name")
|
||||
}
|
||||
|
||||
return &VariableExpression{src, name}, nil
|
||||
}
|
||||
|
||||
func NewVariableDeclarationExpression(src core.SourceMap, name string, init core.Expression) (*VariableDeclarationExpression, error) {
|
||||
v, err := NewVariableExpression(src, name)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if core.IsNil(init) {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "missed variable initializer")
|
||||
}
|
||||
|
||||
return &VariableDeclarationExpression{v, init}, nil
|
||||
}
|
||||
|
||||
func (e *VariableExpression) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||
value, err := e.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, core.SourceError(e.src, err)
|
||||
}
|
||||
|
||||
iter, err := collections.ToIterator(value)
|
||||
|
||||
if err != nil {
|
||||
return nil, core.SourceError(e.src, err)
|
||||
}
|
||||
|
||||
return iter, nil
|
||||
}
|
||||
|
||||
func (e *VariableExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
return scope.GetVariable(e.name)
|
||||
}
|
||||
|
||||
func (e *VariableDeclarationExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
val, err := e.init.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
return values.None, scope.SetVariable(e.name, val)
|
||||
}
|
47
pkg/runtime/options.go
Normal file
47
pkg/runtime/options.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package runtime
|
||||
|
||||
import "context"
|
||||
|
||||
type (
|
||||
Options struct {
|
||||
proxy string
|
||||
cdp string
|
||||
variables map[string]interface{}
|
||||
}
|
||||
|
||||
Option func(*Options)
|
||||
)
|
||||
|
||||
func newOptions() *Options {
|
||||
return &Options{
|
||||
cdp: "",
|
||||
variables: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
func WithParam(name string, value interface{}) Option {
|
||||
return func(options *Options) {
|
||||
options.variables[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
func WithBrowser(address string) Option {
|
||||
return func(options *Options) {
|
||||
options.cdp = address
|
||||
}
|
||||
}
|
||||
|
||||
func WithProxy(address string) Option {
|
||||
return func(options *Options) {
|
||||
// TODO: add implementation
|
||||
options.proxy = address
|
||||
}
|
||||
}
|
||||
|
||||
func (opts *Options) withContext(parent context.Context) context.Context {
|
||||
return context.WithValue(
|
||||
parent,
|
||||
"variables",
|
||||
opts.variables,
|
||||
)
|
||||
}
|
42
pkg/runtime/program.go
Normal file
42
pkg/runtime/program.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/html/driver"
|
||||
)
|
||||
|
||||
type Program struct {
|
||||
exp core.Expression
|
||||
}
|
||||
|
||||
func NewProgram(exp core.Expression) *Program {
|
||||
return &Program{exp}
|
||||
}
|
||||
|
||||
func (p *Program) Run(ctx context.Context, setters ...Option) ([]byte, error) {
|
||||
scope, closeFn := core.NewRootScope()
|
||||
|
||||
defer closeFn()
|
||||
|
||||
opts := newOptions()
|
||||
|
||||
for _, setter := range setters {
|
||||
setter(opts)
|
||||
}
|
||||
|
||||
ctx = opts.withContext(ctx)
|
||||
ctx = driver.WithCdpDriver(ctx, opts.cdp)
|
||||
ctx = driver.WithHttpDriver(ctx)
|
||||
|
||||
out, err := p.exp.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
js, _ := values.None.MarshalJSON()
|
||||
|
||||
return js, err
|
||||
}
|
||||
|
||||
return out.MarshalJSON()
|
||||
}
|
138
pkg/runtime/values/array.go
Normal file
138
pkg/runtime/values/array.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package values
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/json"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
ArrayPredicate = func(value core.Value, idx int) bool
|
||||
Array struct {
|
||||
value []core.Value
|
||||
}
|
||||
)
|
||||
|
||||
func NewArray(size int) *Array {
|
||||
return &Array{value: make([]core.Value, 0, size)}
|
||||
}
|
||||
|
||||
func NewArrayWith(values ...core.Value) *Array {
|
||||
return &Array{value: values}
|
||||
}
|
||||
|
||||
func (t *Array) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(t.value)
|
||||
}
|
||||
|
||||
func (t *Array) Type() core.Type {
|
||||
return core.ArrayType
|
||||
}
|
||||
|
||||
func (t *Array) String() string {
|
||||
marshaled, err := t.MarshalJSON()
|
||||
|
||||
if err != nil {
|
||||
return "[]"
|
||||
}
|
||||
|
||||
return string(marshaled)
|
||||
}
|
||||
|
||||
func (t *Array) Compare(other core.Value) int {
|
||||
switch other.Type() {
|
||||
case core.ArrayType:
|
||||
arr := other.(*Array)
|
||||
|
||||
if t.Length() == 0 && arr.Length() == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var res = 1
|
||||
|
||||
for _, val := range t.value {
|
||||
arr.ForEach(func(otherVal core.Value, idx int) bool {
|
||||
res = val.Compare(otherVal)
|
||||
|
||||
return res != -1
|
||||
})
|
||||
}
|
||||
|
||||
return res
|
||||
case core.ObjectType:
|
||||
return -1
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Array) Unwrap() interface{} {
|
||||
arr := make([]interface{}, t.Length())
|
||||
|
||||
for idx, val := range t.value {
|
||||
arr[idx] = val.Unwrap()
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
|
||||
func (t *Array) Hash() int {
|
||||
bytes, err := t.MarshalJSON()
|
||||
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
h := sha512.New()
|
||||
|
||||
out, err := h.Write(bytes)
|
||||
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (t *Array) Length() Int {
|
||||
return Int(len(t.value))
|
||||
}
|
||||
|
||||
func (t *Array) ForEach(predicate ArrayPredicate) {
|
||||
for idx, val := range t.value {
|
||||
if predicate(val, idx) == false {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Array) Get(idx Int) core.Value {
|
||||
l := len(t.value) - 1
|
||||
|
||||
if int(idx) > l {
|
||||
return None
|
||||
}
|
||||
|
||||
return t.value[idx]
|
||||
}
|
||||
|
||||
func (t *Array) Set(idx Int, value core.Value) error {
|
||||
last := len(t.value) - 1
|
||||
|
||||
if last >= int(idx) {
|
||||
t.value[idx] = value
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.Wrap(core.ErrInvalidOperation, "out of bounds")
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
274
pkg/runtime/values/array_test.go
Normal file
274
pkg/runtime/values/array_test.go
Normal file
@@ -0,0 +1,274 @@
|
||||
package values_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestArray(t *testing.T) {
|
||||
Convey("#constructor", t, func() {
|
||||
Convey("Should create an empty array", func() {
|
||||
arr := values.NewArray(10)
|
||||
|
||||
So(arr.Length(), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should create an array, from passed values", func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
)
|
||||
|
||||
So(arr.Length(), ShouldEqual, 3)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".MarshalJSON", t, func() {
|
||||
Convey("Should serialize empty array", func() {
|
||||
arr := values.NewArray(10)
|
||||
marshaled, err := arr.MarshalJSON()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(string(marshaled), ShouldEqual, "[]")
|
||||
})
|
||||
|
||||
Convey("Should serialize full array", func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
)
|
||||
marshaled, err := json.Marshal(arr)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(string(marshaled), ShouldEqual, "[1,2,3]")
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".Type", t, func() {
|
||||
Convey("Should return type", func() {
|
||||
arr := values.NewArray(1)
|
||||
|
||||
So(arr.Type(), ShouldEqual, core.ArrayType)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".Unwrap", t, func() {
|
||||
Convey("Should return a an array of unwrapped values", func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.ZeroInt,
|
||||
values.ZeroInt,
|
||||
)
|
||||
|
||||
for _, val := range arr.Unwrap().([]interface{}) {
|
||||
So(val, ShouldHaveSameTypeAs, 0)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".String", t, func() {
|
||||
Convey("Should return a string representation ", func() {
|
||||
arr := values.NewArrayWith(values.ZeroInt, values.ZeroInt)
|
||||
|
||||
So(arr.String(), ShouldEqual, "[0,0]")
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".Compare", t, func() {
|
||||
Convey("It should return 1 for all non-array and non-object values", func() {
|
||||
arr := values.NewArrayWith(values.ZeroInt, values.ZeroInt)
|
||||
|
||||
So(arr.Compare(values.None), ShouldEqual, 1)
|
||||
So(arr.Compare(values.ZeroInt), ShouldEqual, 1)
|
||||
So(arr.Compare(values.ZeroFloat), ShouldEqual, 1)
|
||||
So(arr.Compare(values.EmptyString), ShouldEqual, 1)
|
||||
})
|
||||
|
||||
Convey("It should return -1 for all object values", func() {
|
||||
arr := values.NewArrayWith(values.ZeroInt, values.ZeroInt)
|
||||
obj := values.NewObject()
|
||||
|
||||
So(arr.Compare(obj), ShouldEqual, -1)
|
||||
})
|
||||
|
||||
Convey("It should return 0 when both arrays are empty", func() {
|
||||
arr1 := values.NewArray(1)
|
||||
arr2 := values.NewArray(1)
|
||||
|
||||
So(arr1.Compare(arr2), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("It should return 1 when other array is empty", func() {
|
||||
arr1 := values.NewArrayWith(values.ZeroFloat)
|
||||
arr2 := values.NewArray(1)
|
||||
|
||||
So(arr1.Compare(arr2), ShouldEqual, 1)
|
||||
})
|
||||
|
||||
Convey("It should return 1 when values are bigger", func() {
|
||||
arr1 := values.NewArrayWith(values.NewInt(1))
|
||||
arr2 := values.NewArrayWith(values.ZeroInt)
|
||||
|
||||
So(arr1.Compare(arr2), ShouldEqual, 1)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".Length", t, func() {
|
||||
Convey("Should return 0 when empty", func() {
|
||||
arr := values.NewArray(1)
|
||||
|
||||
So(arr.Length(), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should return greater than 0 when not empty", func() {
|
||||
arr := values.NewArrayWith(values.ZeroInt, values.ZeroInt)
|
||||
|
||||
So(arr.Length(), ShouldEqual, 2)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".ForEach", t, func() {
|
||||
Convey("Should iterate over elements", func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
)
|
||||
counter := 0
|
||||
|
||||
arr.ForEach(func(value core.Value, idx int) bool {
|
||||
counter += 1
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
So(counter, ShouldEqual, arr.Length())
|
||||
})
|
||||
|
||||
Convey("Should not iterate when empty", func() {
|
||||
arr := values.NewArrayWith()
|
||||
counter := 0
|
||||
|
||||
arr.ForEach(func(value core.Value, idx int) bool {
|
||||
counter += 1
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
So(counter, ShouldEqual, arr.Length())
|
||||
})
|
||||
|
||||
Convey("Should break iteration when false returned", func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
threshold := 3
|
||||
counter := 0
|
||||
|
||||
arr.ForEach(func(value core.Value, idx int) bool {
|
||||
counter += 1
|
||||
|
||||
return value.Compare(values.NewInt(threshold)) == -1
|
||||
})
|
||||
|
||||
So(counter, ShouldEqual, threshold)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".Get", t, func() {
|
||||
Convey("Should return item by index", func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
el := arr.Get(1)
|
||||
|
||||
So(el.Compare(values.NewInt(2)), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should return None when no value", func() {
|
||||
arr := values.NewArrayWith()
|
||||
|
||||
el := arr.Get(1)
|
||||
|
||||
So(el.Compare(values.None), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".Set", t, func() {
|
||||
Convey("Should set item by index", func() {
|
||||
arr := values.NewArrayWith(values.ZeroInt)
|
||||
|
||||
err := arr.Set(0, values.NewInt(1))
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(arr.Length(), ShouldEqual, 1)
|
||||
So(arr.Get(0).Compare(values.NewInt(1)), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should return an error when index is out of bounds", func() {
|
||||
arr := values.NewArray(10)
|
||||
|
||||
err := arr.Set(0, values.NewInt(1))
|
||||
|
||||
So(err, ShouldNotBeNil)
|
||||
So(arr.Length(), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".Push", t, func() {
|
||||
Convey("Should add an item", func() {
|
||||
arr := values.NewArray(10)
|
||||
|
||||
src := []core.Value{
|
||||
values.ZeroInt,
|
||||
values.ZeroInt,
|
||||
values.ZeroInt,
|
||||
values.ZeroInt,
|
||||
values.ZeroInt,
|
||||
}
|
||||
|
||||
for _, val := range src {
|
||||
arr.Push(val)
|
||||
}
|
||||
|
||||
So(arr.Length(), ShouldEqual, len(src))
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".Slice", t, func() {
|
||||
Convey("Should return a slice", func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(0),
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
s := arr.Slice(0, 1)
|
||||
|
||||
So(len(s), ShouldEqual, 1)
|
||||
So(s[0].Compare(values.ZeroInt), ShouldEqual, 0)
|
||||
|
||||
s2 := arr.Slice(2, arr.Length())
|
||||
|
||||
So(len(s2), ShouldEqual, arr.Length()-2)
|
||||
})
|
||||
})
|
||||
}
|
79
pkg/runtime/values/binary.go
Normal file
79
pkg/runtime/values/binary.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package values
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/json"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
type Binary struct {
|
||||
values []byte
|
||||
}
|
||||
|
||||
func NewBinary(values []byte) *Binary {
|
||||
return &Binary{values}
|
||||
}
|
||||
|
||||
func NewBinaryFrom(stream io.Reader) (*Binary, error) {
|
||||
values, err := ioutil.ReadAll(stream)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Binary{values}, nil
|
||||
}
|
||||
|
||||
func (b *Binary) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(b.values)
|
||||
}
|
||||
|
||||
func (b *Binary) Type() core.Type {
|
||||
return core.BinaryType
|
||||
}
|
||||
|
||||
func (b *Binary) String() string {
|
||||
return string(b.values)
|
||||
}
|
||||
|
||||
func (b *Binary) Compare(other core.Value) int {
|
||||
// TODO: Lame comparison, need to think more about it
|
||||
switch other.Type() {
|
||||
case core.BooleanType:
|
||||
b2 := other.(*Binary)
|
||||
|
||||
if b2.Length() == b.Length() {
|
||||
return 0
|
||||
}
|
||||
|
||||
if b.Length() > b2.Length() {
|
||||
return 1
|
||||
}
|
||||
|
||||
return -1
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Binary) Unwrap() interface{} {
|
||||
return b.values
|
||||
}
|
||||
|
||||
func (b *Binary) Hash() int {
|
||||
h := sha512.New()
|
||||
|
||||
out, err := h.Write(b.values)
|
||||
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (b *Binary) Length() Int {
|
||||
return NewInt(len(b.values))
|
||||
}
|
111
pkg/runtime/values/boolean.go
Normal file
111
pkg/runtime/values/boolean.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package values
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/json"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/pkg/errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Boolean bool
|
||||
|
||||
var False = Boolean(false)
|
||||
var True = Boolean(true)
|
||||
|
||||
func NewBoolean(input bool) Boolean {
|
||||
return Boolean(input)
|
||||
}
|
||||
|
||||
func ParseBoolean(input interface{}) (Boolean, error) {
|
||||
b, ok := input.(bool)
|
||||
|
||||
if ok {
|
||||
if b {
|
||||
return True, nil
|
||||
}
|
||||
|
||||
return False, nil
|
||||
}
|
||||
|
||||
s, ok := input.(string)
|
||||
|
||||
if ok {
|
||||
s := strings.ToLower(s)
|
||||
|
||||
if s == "true" {
|
||||
return True, nil
|
||||
}
|
||||
|
||||
if s == "false" {
|
||||
return False, nil
|
||||
}
|
||||
}
|
||||
|
||||
return False, errors.Wrap(core.ErrInvalidType, "expected 'bool'")
|
||||
}
|
||||
|
||||
func ParseBooleanP(input interface{}) Boolean {
|
||||
res, err := ParseBoolean(input)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (t Boolean) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(bool(t))
|
||||
}
|
||||
|
||||
func (t Boolean) Type() core.Type {
|
||||
return core.BooleanType
|
||||
}
|
||||
|
||||
func (t Boolean) String() string {
|
||||
if t {
|
||||
return "true"
|
||||
}
|
||||
|
||||
return "false"
|
||||
}
|
||||
|
||||
func (t Boolean) Compare(other core.Value) int {
|
||||
raw := bool(t)
|
||||
|
||||
switch other.Type() {
|
||||
case core.BooleanType:
|
||||
i := other.Unwrap().(bool)
|
||||
|
||||
if raw == i {
|
||||
return 0
|
||||
}
|
||||
|
||||
if raw == false && i == true {
|
||||
return -1
|
||||
}
|
||||
|
||||
return +1
|
||||
case core.NoneType:
|
||||
return 1
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
func (t Boolean) Unwrap() interface{} {
|
||||
return bool(t)
|
||||
}
|
||||
|
||||
func (t Boolean) Hash() int {
|
||||
h := sha512.New()
|
||||
|
||||
out, err := h.Write([]byte(t.String()))
|
||||
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
73
pkg/runtime/values/boolean_test.go
Normal file
73
pkg/runtime/values/boolean_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package values_test
|
||||
|
||||
import (
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBoolean(t *testing.T) {
|
||||
Convey(".MarshalJSON", t, func() {
|
||||
Convey("Should serialize a boolean value", func() {
|
||||
b := values.True
|
||||
marshaled, err := b.MarshalJSON()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(string(marshaled), ShouldEqual, "true")
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".Type", t, func() {
|
||||
Convey("Should return a type", func() {
|
||||
So(values.True.Type(), ShouldEqual, core.BooleanType)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".Unwrap", t, func() {
|
||||
Convey("Should return an unwrapped value", func() {
|
||||
So(values.True.Unwrap(), ShouldHaveSameTypeAs, true)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".String", t, func() {
|
||||
Convey("Should return a string representation ", func() {
|
||||
So(values.True.String(), ShouldEqual, "true")
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".Compare", t, func() {
|
||||
Convey("It should return 1 when compared to None", func() {
|
||||
So(values.True.Compare(values.None), ShouldEqual, 1)
|
||||
})
|
||||
|
||||
Convey("It should return -1 for all non-boolean values", func() {
|
||||
vals := []core.Value{
|
||||
values.NewString("foo"),
|
||||
values.NewInt(1),
|
||||
values.NewFloat(1.1),
|
||||
values.NewArray(10),
|
||||
values.NewObject(),
|
||||
}
|
||||
|
||||
for _, v := range vals {
|
||||
So(values.True.Compare(v), ShouldEqual, -1)
|
||||
So(values.False.Compare(v), ShouldEqual, -1)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("It should return 0 when both are True or False", func() {
|
||||
So(values.True.Compare(values.True), ShouldEqual, 0)
|
||||
So(values.False.Compare(values.False), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("It should return 1 when other is false", func() {
|
||||
So(values.True.Compare(values.False), ShouldEqual, 1)
|
||||
})
|
||||
|
||||
Convey("It should return -1 when other are true", func() {
|
||||
So(values.False.Compare(values.True), ShouldEqual, -1)
|
||||
})
|
||||
})
|
||||
}
|
99
pkg/runtime/values/date_time.go
Normal file
99
pkg/runtime/values/date_time.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package values
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"time"
|
||||
)
|
||||
|
||||
const defaultTimeLayout = time.RFC3339
|
||||
|
||||
type DateTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func NewDateTime(time time.Time) DateTime {
|
||||
return DateTime{time}
|
||||
}
|
||||
|
||||
func ParseDateTime(input interface{}) (DateTime, error) {
|
||||
return ParseDateTimeWith(input, defaultTimeLayout)
|
||||
}
|
||||
|
||||
func ParseDateTimeWith(input interface{}, layout string) (DateTime, error) {
|
||||
switch input.(type) {
|
||||
case string:
|
||||
t, err := time.Parse(layout, input.(string))
|
||||
|
||||
if err != nil {
|
||||
return DateTime{time.Now()}, err
|
||||
}
|
||||
|
||||
return DateTime{t}, nil
|
||||
default:
|
||||
return DateTime{time.Now()}, core.ErrInvalidType
|
||||
}
|
||||
}
|
||||
|
||||
func ParseDateTimeP(input interface{}) DateTime {
|
||||
dt, err := ParseDateTime(input)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return dt
|
||||
}
|
||||
|
||||
func (t DateTime) MarshalJSON() ([]byte, error) {
|
||||
return t.Time.MarshalJSON()
|
||||
}
|
||||
|
||||
func (t DateTime) Type() core.Type {
|
||||
return core.DateTimeType
|
||||
}
|
||||
|
||||
func (t DateTime) String() string {
|
||||
return t.Time.String()
|
||||
}
|
||||
|
||||
func (t DateTime) Compare(other core.Value) int {
|
||||
switch other.Type() {
|
||||
case core.DateTimeType:
|
||||
other := other.(DateTime)
|
||||
|
||||
if t.After(other.Time) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if t.Before(other.Time) {
|
||||
return -1
|
||||
}
|
||||
|
||||
return 0
|
||||
default:
|
||||
if other.Type() > core.DateTimeType {
|
||||
return -1
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func (t DateTime) Unwrap() interface{} {
|
||||
return t.Time
|
||||
}
|
||||
|
||||
func (t DateTime) Hash() int {
|
||||
h := sha512.New()
|
||||
|
||||
t.Time.MarshalJSON()
|
||||
|
||||
out, err := h.Write([]byte(t.Time.String()))
|
||||
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
131
pkg/runtime/values/float.go
Normal file
131
pkg/runtime/values/float.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package values
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/pkg/errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Float float64
|
||||
|
||||
var ZeroFloat = Float(0.0)
|
||||
|
||||
func NewFloat(input float64) Float {
|
||||
return Float(input)
|
||||
}
|
||||
|
||||
func ParseFloat(input interface{}) (Float, error) {
|
||||
if core.IsNil(input) {
|
||||
return ZeroFloat, nil
|
||||
}
|
||||
|
||||
i, ok := input.(float64)
|
||||
|
||||
if ok {
|
||||
if i == 0 {
|
||||
return ZeroFloat, nil
|
||||
}
|
||||
|
||||
return Float(i), nil
|
||||
}
|
||||
|
||||
// try to cast
|
||||
str, ok := input.(string)
|
||||
|
||||
if ok {
|
||||
i, err := strconv.Atoi(str)
|
||||
|
||||
if err == nil {
|
||||
if i == 0 {
|
||||
return ZeroFloat, nil
|
||||
}
|
||||
|
||||
return Float(i), nil
|
||||
}
|
||||
}
|
||||
|
||||
return ZeroFloat, errors.Wrap(core.ErrInvalidType, "expected 'float'")
|
||||
}
|
||||
|
||||
func ParseFloatP(input interface{}) Float {
|
||||
res, err := ParseFloat(input)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (t Float) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(float64(t))
|
||||
}
|
||||
|
||||
func (t Float) Type() core.Type {
|
||||
return core.FloatType
|
||||
}
|
||||
|
||||
func (t Float) String() string {
|
||||
return fmt.Sprintf("%f", t)
|
||||
}
|
||||
|
||||
func (t Float) Compare(other core.Value) int {
|
||||
raw := float64(t)
|
||||
|
||||
switch other.Type() {
|
||||
case core.FloatType:
|
||||
f := other.Unwrap().(float64)
|
||||
|
||||
if raw == f {
|
||||
return 0
|
||||
}
|
||||
|
||||
if raw < f {
|
||||
return -1
|
||||
}
|
||||
|
||||
return +1
|
||||
case core.IntType:
|
||||
i := other.Unwrap().(int)
|
||||
f := float64(i)
|
||||
|
||||
if raw == f {
|
||||
return 0
|
||||
}
|
||||
|
||||
if raw < f {
|
||||
return -1
|
||||
}
|
||||
|
||||
return +1
|
||||
case core.BooleanType, core.NoneType:
|
||||
return 1
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
func (t Float) Unwrap() interface{} {
|
||||
return float64(t)
|
||||
}
|
||||
|
||||
func (t Float) Hash() int {
|
||||
bytes, err := t.MarshalJSON()
|
||||
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
h := sha512.New()
|
||||
|
||||
out, err := h.Write(bytes)
|
||||
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
211
pkg/runtime/values/helpers.go
Normal file
211
pkg/runtime/values/helpers.go
Normal file
@@ -0,0 +1,211 @@
|
||||
package values
|
||||
|
||||
import (
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetIn(from core.Value, byPath []core.Value) (core.Value, error) {
|
||||
if byPath == nil || len(byPath) == 0 {
|
||||
return None, nil
|
||||
}
|
||||
|
||||
var result = from
|
||||
var err error
|
||||
|
||||
for _, segment := range byPath {
|
||||
if result == None || result == nil {
|
||||
break
|
||||
}
|
||||
|
||||
segmentType := segment.Type()
|
||||
|
||||
switch result.Type() {
|
||||
case core.ObjectType:
|
||||
obj := result.(*Object)
|
||||
|
||||
if segmentType != core.StringType {
|
||||
return nil, core.TypeError(segmentType, core.StringType)
|
||||
}
|
||||
|
||||
result, _ = obj.Get(segment.(String))
|
||||
|
||||
break
|
||||
case core.ArrayType:
|
||||
arr := result.(*Array)
|
||||
|
||||
if segmentType != core.IntType {
|
||||
return nil, core.TypeError(segmentType, core.IntType)
|
||||
}
|
||||
|
||||
result = arr.Get(segment.(Int))
|
||||
|
||||
break
|
||||
case core.HtmlElementType, core.HtmlDocumentType:
|
||||
el := result.(HtmlNode)
|
||||
|
||||
if segmentType == core.IntType {
|
||||
result = el.GetChildNode(segment.(Int))
|
||||
} else if segmentType == core.StringType {
|
||||
strSegment := segment.(String)
|
||||
|
||||
switch strSegment {
|
||||
case "nodeType":
|
||||
result = el.NodeType()
|
||||
case "nodeName":
|
||||
result = el.NodeName()
|
||||
case "innerText":
|
||||
result = el.InnerText()
|
||||
case "innerHtml":
|
||||
result = el.InnerHtml()
|
||||
case "value":
|
||||
result = el.Value()
|
||||
case "attributes":
|
||||
result = el.GetAttributes()
|
||||
case "children":
|
||||
result = el.GetChildNodes()
|
||||
case "length":
|
||||
result = el.Length()
|
||||
default:
|
||||
result = None
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return None, err
|
||||
}
|
||||
} else {
|
||||
return nil, core.TypeError(segmentType, core.IntType, core.StringType)
|
||||
}
|
||||
|
||||
default:
|
||||
return None, core.TypeError(
|
||||
from.Type(),
|
||||
core.ArrayType,
|
||||
core.ObjectType,
|
||||
core.HtmlDocumentType,
|
||||
core.HtmlElementType,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func SetIn(to core.Value, byPath []core.Value, value core.Value) error {
|
||||
if byPath == nil || len(byPath) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var parent core.Value
|
||||
var current = to
|
||||
target := len(byPath) - 1
|
||||
|
||||
for idx, segment := range byPath {
|
||||
parent = current
|
||||
isTarget := target == idx
|
||||
segmentType := segment.Type()
|
||||
|
||||
switch parent.Type() {
|
||||
case core.ObjectType:
|
||||
parent := parent.(*Object)
|
||||
|
||||
if segmentType != core.StringType {
|
||||
return core.TypeError(segmentType, core.StringType)
|
||||
}
|
||||
|
||||
if isTarget == false {
|
||||
current, _ = parent.Get(segment.(String))
|
||||
} else {
|
||||
parent.Set(segment.(String), value)
|
||||
}
|
||||
|
||||
break
|
||||
case core.ArrayType:
|
||||
if segmentType != core.IntType {
|
||||
return core.TypeError(segmentType, core.IntType)
|
||||
}
|
||||
|
||||
parent := parent.(*Array)
|
||||
|
||||
if isTarget == false {
|
||||
current = parent.Get(segment.(Int))
|
||||
} else {
|
||||
parent.Set(segment.(Int), value)
|
||||
}
|
||||
|
||||
break
|
||||
default:
|
||||
// redefine parent
|
||||
isArray := segmentType == core.IntType
|
||||
|
||||
// it's not an index
|
||||
if isArray == false {
|
||||
obj := NewObject()
|
||||
parent = obj
|
||||
|
||||
if segmentType != core.StringType {
|
||||
return core.TypeError(segmentType, core.StringType)
|
||||
}
|
||||
|
||||
if isTarget {
|
||||
obj.Set(segment.(String), value)
|
||||
}
|
||||
} else {
|
||||
arr := NewArray(10)
|
||||
parent = arr
|
||||
|
||||
if isTarget {
|
||||
arr.Set(segment.(Int), value)
|
||||
}
|
||||
}
|
||||
|
||||
// set new parent
|
||||
SetIn(to, byPath[0:idx-1], parent)
|
||||
|
||||
if isTarget == false {
|
||||
current = None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Parse(input interface{}) core.Value {
|
||||
switch input.(type) {
|
||||
case bool:
|
||||
return NewBoolean(input.(bool))
|
||||
case string:
|
||||
return NewString(input.(string))
|
||||
case int:
|
||||
return NewInt(input.(int))
|
||||
case float64:
|
||||
return NewFloat(input.(float64))
|
||||
case float32:
|
||||
return NewFloat(float64(input.(float32)))
|
||||
case time.Time:
|
||||
return NewDateTime(input.(time.Time))
|
||||
case []interface{}:
|
||||
input := input.([]interface{})
|
||||
arr := NewArray(len(input))
|
||||
|
||||
for _, el := range input {
|
||||
arr.Push(Parse(el))
|
||||
}
|
||||
|
||||
return arr
|
||||
case map[string]interface{}:
|
||||
input := input.(map[string]interface{})
|
||||
obj := NewObject()
|
||||
|
||||
for key, el := range input {
|
||||
obj.Set(NewString(key), Parse(el))
|
||||
}
|
||||
|
||||
return obj
|
||||
case []byte:
|
||||
return NewBinary(input.([]byte))
|
||||
}
|
||||
|
||||
return None
|
||||
}
|
31
pkg/runtime/values/html.go
Normal file
31
pkg/runtime/values/html.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package values
|
||||
|
||||
import "github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
|
||||
type HtmlNode interface {
|
||||
core.Value
|
||||
|
||||
NodeType() Int
|
||||
|
||||
NodeName() String
|
||||
|
||||
Length() Int
|
||||
|
||||
InnerText() String
|
||||
|
||||
InnerHtml() String
|
||||
|
||||
Value() core.Value
|
||||
|
||||
GetAttributes() core.Value
|
||||
|
||||
GetAttribute(name String) core.Value
|
||||
|
||||
GetChildNodes() core.Value
|
||||
|
||||
GetChildNode(idx Int) core.Value
|
||||
|
||||
QuerySelector(selector String) core.Value
|
||||
|
||||
QuerySelectorAll(selector String) core.Value
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user