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

Hello world

This commit is contained in:
Tim Voronov
2018-09-18 16:42:38 -04:00
parent c872f4565b
commit e02e861240
132 changed files with 22163 additions and 2 deletions

1
.env Normal file
View File

@@ -0,0 +1 @@
ADDR=8080

126
.gitignore vendored Normal file
View 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
View 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
View 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
View 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}/...

View File

@@ -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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,5 @@
package main
func main() {
}

1
docs/README.md Normal file
View File

@@ -0,0 +1 @@
# Docs

View 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
View 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 }

View 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
View 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.

View 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.

View 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.

View 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.

View 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.

View 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
View 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
View 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
View 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
View 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
View 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
View 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
}

File diff suppressed because it is too large Load Diff

12
pkg/compiler/errors.go Normal file
View 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
View 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
View 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
View 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
View 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(),
)
}

View 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: '\'' ('\\'. | '\'\'' | ~('\'' | '\\'))* '\'';

View 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

View 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
;

File diff suppressed because one or more lines are too long

View 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

File diff suppressed because one or more lines are too long

View 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
View 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

File diff suppressed because it is too large Load Diff

View 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) {}

View 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)
}

View 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)
}

View 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
View 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))
}

View File

@@ -0,0 +1,7 @@
package collections
import "github.com/MontFerret/ferret/pkg/runtime/values"
type Collection interface {
Length() values.Int
}

View 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")
)

View 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
}
}

View 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]`)
})
}

View 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"}]}`)
})
}

View 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
}

View 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
}

View 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)
})
}

View 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()
}
}

View 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)
})
}

View 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)
}
)

View 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
}

View 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}]`)
})
}

View 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)
}

View File

@@ -0,0 +1,7 @@
package core
import "context"
type Expression interface {
Exec(ctx context.Context, scope *Scope) (Value, error)
}

View 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
}

View 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
}
}

View 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
View 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
}

View 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
View 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
}

View 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
}

View 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)
}

View 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
})
}

View 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)
}

View 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...)
}

View 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
}

View 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...)
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
View 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
View 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
View 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]
}

View 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)
})
})
}

View 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))
}

View 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
}

View 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)
})
})
}

View 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
View 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
}

View 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
}

View 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