diff --git a/src/cmd/goblin/main.go b/src/cmd/goblin/main.go index 258ab22..fc66c32 100644 --- a/src/cmd/goblin/main.go +++ b/src/cmd/goblin/main.go @@ -29,6 +29,7 @@ import ( "github.com/surdeus/goblin/src/tool/wc" "github.com/surdeus/goblin/src/tool/whoami" "github.com/surdeus/goblin/src/tool/yes" + "github.com/surdeus/goblin/src/tool/run" "github.com/surdeus/gomtool/src/mtool" ) @@ -74,6 +75,11 @@ func main() { "link files", "", }, + "run": mtool.Tool{ + run.Run, + "run anko script", + "", + }, } mtool.Main("goblin", tools) diff --git a/src/tool/run/.github/FUNDING.yml b/src/tool/run/.github/FUNDING.yml new file mode 100644 index 0000000..d0701d4 --- /dev/null +++ b/src/tool/run/.github/FUNDING.yml @@ -0,0 +1,8 @@ +# These are supported funding model platforms + +github: mattn # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: mattn # Replace with a single Patreon username +open_collective: mattn # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +custom: # Replace with a single custom sponsorship URL diff --git a/src/tool/run/.travis.yml b/src/tool/run/.travis.yml new file mode 100644 index 0000000..0e000e9 --- /dev/null +++ b/src/tool/run/.travis.yml @@ -0,0 +1,22 @@ +language: go + +go: + - 1.8.x + - 1.9.x + - 1.10.x + - 1.11.x + - 1.12.x + - 1.13.x + - 1.14.x + +env: + secure: "ELC4rD8nn2l5T48WYbTfcbwGGBmNxl7LAu05hgx5AB9/KA+oD3oBKIJkZqD512gJ31Gtyla/hG9QOgU7LikfWdpGuJjVILy01ZqtgP5SSKsrTdlln1D5pK1ZyHJNrEPevb3W5PYn9ahHnjKGtpobXj4/E0sCXfRPH67jv9hffYs=" + +before_install: + - go get -u github.com/haya14busa/goverage + +script: + - goverage -v -coverprofile=coverage.txt -covermode=count ./vm ./env . ./ast/astutil + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/src/tool/run/LICENSE b/src/tool/run/LICENSE new file mode 100644 index 0000000..252f04a --- /dev/null +++ b/src/tool/run/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2018 Yasuhiro Matsumoto, http://mattn.kaoriya.net + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/tool/run/README.md b/src/tool/run/README.md new file mode 100644 index 0000000..b785465 --- /dev/null +++ b/src/tool/run/README.md @@ -0,0 +1,155 @@ +# Anko + +[![GoDoc Reference](https://godoc.org/github.com/surdeus/goblin/src/tool/run/vm?status.svg)](http://godoc.org/github.com/surdeus/goblin/src/tool/run/vm) +[![Build Status](https://travis-ci.org/mattn/anko.svg?branch=master)](https://travis-ci.org/mattn/anko) +[![Financial Contributors on Open Collective](https://opencollective.com/mattn-anko/all/badge.svg?label=financial+contributors)](https://opencollective.com/mattn-anko) [![Coverage](https://codecov.io/gh/mattn/anko/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/anko) +[![Go Report Card](https://goreportcard.com/badge/github.com/surdeus/goblin/src/tool/run)](https://goreportcard.com/report/github.com/surdeus/goblin/src/tool/run) + +Anko is a scriptable interpreter written in Go. + +![](https://raw.githubusercontent.com/mattn/anko/master/anko.png) + +(Picture licensed under CC BY-SA 3.0, photo by Ocdp) + + +## Usage Example - Embedded + +```go +package main + +import ( + "fmt" + "log" + + "github.com/surdeus/goblin/src/tool/run/env" + "github.com/surdeus/goblin/src/tool/run/vm" +) + +func main() { + e := env.NewEnv() + + err := e.Define("println", fmt.Println) + if err != nil { + log.Fatalf("Define error: %v\n", err) + } + + script := ` +println("Hello World :)") +` + + _, err = vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("Execute error: %v\n", err) + } + + // output: Hello World :) +} +``` + +More examples are located in the GoDoc: + +https://godoc.org/github.com/surdeus/goblin/src/tool/run/vm + + +## Usage Example - Command Line + +### Building +``` +go get github.com/surdeus/goblin/src/tool/run +go install github.com/surdeus/goblin/src/tool/run +``` + +### Running an Anko script file named script.ank +``` +./anko script.ank +``` + +## Anko Script Quick Start +``` +// declare variables +x = 1 +y = x + 1 + +// print using outside the script defined println function +println(x + y) // 3 + +// if else statement +if x < 1 || y < 1 { + println(x) +} else if x < 1 && y < 1 { + println(y) +} else { + println(x + y) +} + +// slice +a = []interface{1, 2, 3} +println(a) // [1 2 3] +println(a[0]) // 1 + +// map +a = map[interface]interface{"x": 1} +println(a) // map[x:1] +a.b = 2 +a["c"] = 3 +println(a["b"]) // 2 +println(a.c) // 3 + +// struct +a = make(struct { + A int64, + B float64 +}) +a.A = 4 +a.B = 5.5 +println(a.A) // 4 +println(a.B) // 5.5 + +// function +func a (x) { + println(x + 1) +} +a(5) // 6 +``` + + +## Please note that the master branch is not stable + +The master branch language and API may change at any time. + +To mitigate breaking changes, please use tagged branches. New tagged branches will be created for breaking changes. + + +## Author + +Yasuhiro Matsumoto (a.k.a mattn) + +## Contributors + +### Code Contributors + +This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. + + +### Financial Contributors + +Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/mattn-anko/contribute)] + +#### Individuals + + + +#### Organizations + +Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/mattn-anko/contribute)] + + + + + + + + + + + diff --git a/src/tool/run/_example/scripts/anonymous-call.ank b/src/tool/run/_example/scripts/anonymous-call.ank new file mode 100644 index 0000000..df73133 --- /dev/null +++ b/src/tool/run/_example/scripts/anonymous-call.ank @@ -0,0 +1,9 @@ +#!anko + +func(x) { + return func(y) { + x(y) + } +}(func(z) { + println("Yay!", z) +})("hello world") diff --git a/src/tool/run/_example/scripts/chan.ank b/src/tool/run/_example/scripts/chan.ank new file mode 100644 index 0000000..18d6e78 --- /dev/null +++ b/src/tool/run/_example/scripts/chan.ank @@ -0,0 +1,13 @@ +#!anko + +c = make(chan int64) + +go func() { + c <- 1 + c <- 2 + c <- 3 +}() + +println(<-c) +println(<-c) +println(<-c) diff --git a/src/tool/run/_example/scripts/env.ank b/src/tool/run/_example/scripts/env.ank new file mode 100644 index 0000000..b98b7cc --- /dev/null +++ b/src/tool/run/_example/scripts/env.ank @@ -0,0 +1,9 @@ +#!anko + +var os, runtime = import("os"), import("runtime") + +if runtime.GOOS == "windows" { + println(os.Getenv("USERPROFILE")) +} else { + println(os.Getenv("HOME")) +} diff --git a/src/tool/run/_example/scripts/example.ank b/src/tool/run/_example/scripts/example.ank new file mode 100644 index 0000000..77faad9 --- /dev/null +++ b/src/tool/run/_example/scripts/example.ank @@ -0,0 +1,70 @@ +#!anko + +# declare function +func foo(x){ + return x + 1 +} + +func bar(x ...){ + return len(x) +} + +# declare variables +x = 1 +y = x + 1 + +# print values +println(x * (y + 2 * x + foo(x) / 2)) + +# if/else condition +if foo(y) >= 1 { + println("こんにちわ世界") +} else { + println("Hello, World") +} + +# array type +a = [1,2,3] +println(a) +println(a[2]) +println(len(a)) + +# map type +m = {"foo": "bar", "bar": "baz"} +for k in keys(m) { + println(m[k]) +} + +f = func(a) { + println(a) +} + +f("あんこ") + +f = func(a ...) { + println(a) +} + +f("あんこ", "だいすき") + +println(1 && 2) + +println(bar(1,2,3)) +println("foo") +println(toByteSlice("あいう")) +println(toRuneSlice("あいう")) + +a = 1 +func foo() { + a = 2 +} +foo() +println(a) + +module Foo { + func bar1() { + println("Foo.bar1") + } +} + +println(Foo.bar1()) diff --git a/src/tool/run/_example/scripts/exec.ank b/src/tool/run/_example/scripts/exec.ank new file mode 100644 index 0000000..45b3988 --- /dev/null +++ b/src/tool/run/_example/scripts/exec.ank @@ -0,0 +1,7 @@ +#!anko + +var os, exec = import("os"), import("os/exec") + +cmd = exec.Command("ls", "-la") +cmd.Stdout = os.Stdout +cmd.Run() diff --git a/src/tool/run/_example/scripts/fib-for.ank b/src/tool/run/_example/scripts/fib-for.ank new file mode 100644 index 0000000..e4d8b19 --- /dev/null +++ b/src/tool/run/_example/scripts/fib-for.ank @@ -0,0 +1,15 @@ +#!anko + +func fib(n) { + a, b = 1, 1 + f = [] + for i in range(n) { + f += a + b += a + a = b - a + } + return f +} + + +println(fib(20)) diff --git a/src/tool/run/_example/scripts/fib-recursion.ank b/src/tool/run/_example/scripts/fib-recursion.ank new file mode 100644 index 0000000..01cbab0 --- /dev/null +++ b/src/tool/run/_example/scripts/fib-recursion.ank @@ -0,0 +1,14 @@ +#!anko + +func fib(n) { + if n == 1 { + return [1] + } else if n == 2 { + return [1,1] + } else { + t = fib(n-1) + return t + (t[len(t)-1] + t[len(t)-2]) + } +} + +println(fib(20)) diff --git a/src/tool/run/_example/scripts/for-break-continue.ank b/src/tool/run/_example/scripts/for-break-continue.ank new file mode 100644 index 0000000..2f5185a --- /dev/null +++ b/src/tool/run/_example/scripts/for-break-continue.ank @@ -0,0 +1,12 @@ +#!anko + +for i in [1,2,3,4,5] { + if i == 2 { + continue + } + println(i) + if i > 3 { + break + } + println("foo") +} diff --git a/src/tool/run/_example/scripts/http.ank b/src/tool/run/_example/scripts/http.ank new file mode 100644 index 0000000..eaa62ad --- /dev/null +++ b/src/tool/run/_example/scripts/http.ank @@ -0,0 +1,8 @@ +#!anko + +var http, ioutil = import("net/http"), import("io/ioutil") + +r = http.DefaultClient.Get("http://golang.org/") +b, _ = ioutil.ReadAll(r[0].Body) +printf("%s", toString(b)) +r[0].Body.Close() diff --git a/src/tool/run/_example/scripts/module.ank b/src/tool/run/_example/scripts/module.ank new file mode 100644 index 0000000..2a17d90 --- /dev/null +++ b/src/tool/run/_example/scripts/module.ank @@ -0,0 +1,10 @@ +#!anko + +module Foo { + func bar1() { + println("Foo.bar1") + return 1 + } +} + +println(Foo.bar1()) diff --git a/src/tool/run/_example/scripts/regexp.ank b/src/tool/run/_example/scripts/regexp.ank new file mode 100644 index 0000000..27e2e65 --- /dev/null +++ b/src/tool/run/_example/scripts/regexp.ank @@ -0,0 +1,8 @@ +#!anko + +var regexp = import("regexp") + +for s in regexp.MustCompile(`[\s_]`).Split("foo_bar_baz", -1) { + println(s) +} + diff --git a/src/tool/run/_example/scripts/server.ank b/src/tool/run/_example/scripts/server.ank new file mode 100644 index 0000000..1a7cfc7 --- /dev/null +++ b/src/tool/run/_example/scripts/server.ank @@ -0,0 +1,8 @@ +#!anko + +var http = import("net/http") + +http.HandleFunc("/", func(w, r) { + w.Write(toByteSlice("hello world")) +}) +http.ListenAndServe(":8080", nil) diff --git a/src/tool/run/_example/scripts/signal.ank b/src/tool/run/_example/scripts/signal.ank new file mode 100644 index 0000000..ccd64c5 --- /dev/null +++ b/src/tool/run/_example/scripts/signal.ank @@ -0,0 +1,14 @@ +#!anko + +var os, signal, time = import("os"), import("os/signal"), import("time") + +c = make(chan os.Signal, 1) +signal.Notify(c, os.Interrupt) +go func() { + <-c + println("CTRL-C") + os.Exit(0) +}() + +d, _ = time.ParseDuration("10s") +time.Sleep(d) diff --git a/src/tool/run/_example/scripts/slice.ank b/src/tool/run/_example/scripts/slice.ank new file mode 100644 index 0000000..3216ffb --- /dev/null +++ b/src/tool/run/_example/scripts/slice.ank @@ -0,0 +1,10 @@ +#!anko + +a = make([]int64, 5) + +for i = 0; i < len(a); i++ { + a[i] = i +} +for i in a { + println(i) +} diff --git a/src/tool/run/_example/scripts/socket.ank b/src/tool/run/_example/scripts/socket.ank new file mode 100644 index 0000000..5e4c3f6 --- /dev/null +++ b/src/tool/run/_example/scripts/socket.ank @@ -0,0 +1,26 @@ +#!anko + +var os, net, url, ioutil = import("os"), import("net"), import("net/url"), import("io/ioutil"); + +func connect(uri) { + proxy = os.Getenv("http_proxy") + if proxy != "" { + u, e = url.Parse(proxy) + if e != nil { + return nil, e + } + return net.Dial("tcp", u.Host) + } + return net.Dial("tcp", uri) +} + +c, e = connect("www.google.com:80") +if e != nil { + throw e +} +c.Write(toByteSlice("GET http://www.google.com/ HTTP/1.0\r\n\r\n")) +b, e = ioutil.ReadAll(c) +if e != nil { + throw e +} +printf("%s", b) diff --git a/src/tool/run/_example/scripts/toType.ank b/src/tool/run/_example/scripts/toType.ank new file mode 100644 index 0000000..a3d0a60 --- /dev/null +++ b/src/tool/run/_example/scripts/toType.ank @@ -0,0 +1,123 @@ +#!anko + +# toInt with ints, floats, strings, bools +println("\ntoInt examples:\n===============") +i = 1<<63 - 1 +println("int", i, "toInt:", toInt(i)) + +i = -1 << 63 +println("int", i, "toInt:", toInt(i)) + +f = 3.141592653589793 +println("float", f, "toInt:", toInt(f)) + +f = 1.797693134862315708145274237317043567981e18 +println("float", f, "toInt:", toInt(f)) + +f = -1.797693134862315708145274237317043567981e18 +println("float", f, "toInt:", toInt(f)) + +s = "4611686018427387904" +println("string", s, "toInt:", toInt(s)) + +s = "-9223372036854775808" +println("string", s, "toInt:", toInt(s)) + +s = "3.141592653589793" +println("string", s, "toInt:", toInt(s)) + +s = "1.797693134862315708145274237317043567981e18" +println("string", s, "toInt:", toInt(s)) + +s = "-1.797693134862315708145274237317043567981e18" +println("string", s, "toInt:", toInt(s)) + +s = "1.797693134862315708145274237317043567981e-18" +println("string", s, "toInt:", toInt(s)) + +b = true +println("bool", b, "toInt:", toInt(b)) + +b = false +println("bool", b, "toInt:", toInt(b)) + +println("\ntoFloat examples:\n=================") +i = 1<<63 - 1 +println("int", i, "toFloat:", toFloat(i)) + +i = -1 << 63 +println("int", i, "toFloat:", toFloat(i)) + +s = "4611686018427387904" +println("string", s, "toFloat:", toFloat(s)) + +s = "-9223372036854775808" +println("string", s, "toFloat:", toFloat(s)) + +s = "3.141592653589793" +println("string", s, "toFloat:", toFloat(s)) + +s = "1.797693134862315708145274237317043567981e18" +println("string", s, "toFloat:", toFloat(s)) + +s = "-1.797693134862315708145274237317043567981e18" +println("string", s, "toFloat:", toFloat(s)) + +s = "1.797693134862315708145274237317043567981e-18" +println("string", s, "toFloat:", toFloat(s)) + +b = true +println("bool", b, "toFloat:", toFloat(b)) + +b = false +println("bool", b, "toFloat:", toFloat(b)) + +println("\ntoBool examples:\n================") +i = 1 +println("int", i, "toBool:", toBool(i)) + +i = 0 +println("int", i, "toBool:", toBool(i)) + +i = -1 +println("int", i, "toBool:", toBool(i)) + +f = 1.0 +println("float", f, "toBool:", toBool(f)) + +f = 0.000000000001 +println("float", f, "toBool:", toBool(f)) + +f = 0.0 +println("float", f, "toBool:", toBool(f)) + +f = -0.0 +println("float", f, "toBool:", toBool(f)) + +s = "y" +println("string", s, "toBool:", toBool(s)) + +s = "yEs" +println("string", s, "toBool:", toBool(s)) + +s = "t" +println("string", s, "toBool:", toBool(s)) + +s = "TrUe" +println("string", s, "toBool:", toBool(s)) + +s = "1" +println("string", s, "toBool:", toBool(s)) + +s = "0" +println("string", s, "toBool:", toBool(s)) + +s = "f" +println("string", s, "toBool:", toBool(s)) + +s = "FaLsE" +println("string", s, "toBool:", toBool(s)) + +s = "foobar" +println("string", s, "toBool:", toBool(s)) + diff --git a/src/tool/run/_example/scripts/try-catch.ank b/src/tool/run/_example/scripts/try-catch.ank new file mode 100644 index 0000000..80029b3 --- /dev/null +++ b/src/tool/run/_example/scripts/try-catch.ank @@ -0,0 +1,19 @@ +#!anko + +var http = import("net/http") + +try { + http.Do() +} catch { + println("catch!") +} finally { + println("finally!") +} + +try { + http.Do() +} catch e { + println("catch!", e) +} finally { + println("finally!") +} diff --git a/src/tool/run/_example/scripts/url.ank b/src/tool/run/_example/scripts/url.ank new file mode 100644 index 0000000..4454fc8 --- /dev/null +++ b/src/tool/run/_example/scripts/url.ank @@ -0,0 +1,7 @@ +#!anko + +var url = import("net/url") + +u, _ = url.Parse("http://www.google.com/search?q=こんにちわ世界") +println(u.Path) +println(u.Host) diff --git a/src/tool/run/_example/scripts/z-combinator.ank b/src/tool/run/_example/scripts/z-combinator.ank new file mode 100644 index 0000000..5d61304 --- /dev/null +++ b/src/tool/run/_example/scripts/z-combinator.ank @@ -0,0 +1,15 @@ +#!anko + +func Z(f) { + return (func(x) { + return f(func(y) { + return x(x)(y) + }) + })(func(x) { + return f(func(y) { + return x(x)(y) + }) + }) +} + +println(Z(func(f) { return func(n) { return n == 0 ? 1 : n * f(n - 1) } })(5) == 120) diff --git a/src/tool/run/anko.go b/src/tool/run/anko.go new file mode 100644 index 0000000..84fbebe --- /dev/null +++ b/src/tool/run/anko.go @@ -0,0 +1,172 @@ +// +build !appengine + +package run + +import ( + "bufio" + //"flag" + "fmt" + "io" + "io/ioutil" + "os" + "strings" + + "github.com/surdeus/goblin/src/tool/run/core" + "github.com/surdeus/goblin/src/tool/run/env" + _ "github.com/surdeus/goblin/src/tool/run/packages" + "github.com/surdeus/goblin/src/tool/run/parser" + "github.com/surdeus/goblin/src/tool/run/vm" + "github.com/surdeus/gomtool/src/mtool" +) + +const version = "0.1.8" + +var ( + flagExecute string + file string + args []string + e *env.Env + flag *mtool.Flags +) + +func Run(flagSet *mtool.Flags) { + var exitCode int + + flag = flagSet + + parseFlags() + setupEnv() + if flagExecute != "" || flag.NArg() > 0 { + exitCode = runNonInteractive() + } else { + exitCode = runInteractive() + } + + os.Exit(exitCode) +} + +func parseFlags() { + flagVersion := flag.Bool("v", false, "prints out the version and then exits") + flag.StringVar(&flagExecute, "e", "", "execute the Anko code") + flag.Parse() + + if *flagVersion { + fmt.Println(version) + os.Exit(0) + } + + if flagExecute != "" || flag.NArg() < 1 { + args = flag.Args() + return + } + + file = flag.Arg(0) + args = flag.Args()[1:] +} + +func setupEnv() { + e = env.NewEnv() + e.Define("args", args) + core.Import(e) +} + +func runNonInteractive() int { + var source string + if flagExecute != "" { + source = flagExecute + } else { + sourceBytes, err := ioutil.ReadFile(file) + if err != nil { + fmt.Println("ReadFile error:", err) + return 2 + } + source = string(sourceBytes) + } + + _, err := vm.Execute(e, nil, source) + if err != nil { + fmt.Println("Execute error:", err) + return 4 + } + + return 0 +} + +func runInteractive() int { + var following bool + var source string + scanner := bufio.NewScanner(os.Stdin) + + parser.EnableErrorVerbose() + + for { + if following { + source += "\n" + fmt.Print(" ") + } else { + fmt.Print("> ") + } + + if !scanner.Scan() { + break + } + source += scanner.Text() + if source == "" { + continue + } + if source == "quit()" { + break + } + + stmts, err := parser.ParseSrc(source) + + if e, ok := err.(*parser.Error); ok { + es := e.Error() + if strings.HasPrefix(es, "syntax error: unexpected") { + if strings.HasPrefix(es, "syntax error: unexpected $end,") { + following = true + continue + } + } else { + if e.Pos.Column == len(source) && !e.Fatal { + fmt.Fprintln(os.Stderr, e) + following = true + continue + } + if e.Error() == "unexpected EOF" { + following = true + continue + } + } + } + + following = false + source = "" + var v interface{} + + if err == nil { + v, err = vm.Run(e, nil, stmts) + } + if err != nil { + if e, ok := err.(*vm.Error); ok { + fmt.Fprintf(os.Stderr, "%d:%d %s\n", e.Pos.Line, e.Pos.Column, err) + } else if e, ok := err.(*parser.Error); ok { + fmt.Fprintf(os.Stderr, "%d:%d %s\n", e.Pos.Line, e.Pos.Column, err) + } else { + fmt.Fprintln(os.Stderr, err) + } + continue + } + + fmt.Printf("%#v\n", v) + } + + if err := scanner.Err(); err != nil { + if err != io.EOF { + fmt.Fprintln(os.Stderr, "ReadString error:", err) + return 12 + } + } + + return 0 +} diff --git a/src/tool/run/anko.png b/src/tool/run/anko.png new file mode 100644 index 0000000..44052a8 Binary files /dev/null and b/src/tool/run/anko.png differ diff --git a/src/tool/run/anko_test.go b/src/tool/run/anko_test.go new file mode 100644 index 0000000..9c7fe8a --- /dev/null +++ b/src/tool/run/anko_test.go @@ -0,0 +1,273 @@ +// +build !appengine + +package run + +import ( + "bufio" + "io" + "log" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + "time" +) + +var logger *log.Logger + +func TestMain(m *testing.M) { + parseFlags() + code := m.Run() + os.Exit(code) +} + +func TestRunNonInteractiveFile(t *testing.T) { + _, filename, _, _ := runtime.Caller(0) + testDir := filepath.Join(filepath.Dir(filename), "core", "testdata") + setupEnv() + + file = filepath.Join(testDir, "not-found.ank") + exitCode := runNonInteractive() + if exitCode != 2 { + t.Fatalf("exitCode - received: %v - expected: %v", exitCode, 2) + } + + file = filepath.Join(testDir, "broken.ank") + exitCode = runNonInteractive() + os.Args = []string{os.Args[0]} + if exitCode != 4 { + t.Fatalf("exitCode - received: %v - expected: %v", exitCode, 4) + } + + file = filepath.Join(testDir, "test.ank") + exitCode = runNonInteractive() + os.Args = []string{os.Args[0]} + if exitCode != 0 { + t.Fatalf("exitCode - received: %v - expected: %v", exitCode, 0) + } + + file = "" +} + +func TestRunNonInteractiveExecute(t *testing.T) { + setupEnv() + + flagExecute = "1 + 1" + exitCode := runNonInteractive() + if exitCode != 0 { + t.Fatalf("exitCode - received: %v - expected: %v", exitCode, 0) + } + + flagExecute = "1++" + exitCode = runNonInteractive() + if exitCode != 4 { + t.Fatalf("exitCode - received: %v - expected: %v", exitCode, 4) + } + + flagExecute = "" +} + +type testInteractive struct { + runLines []string + runOutputs []string + runError string +} + +func TestRunInteractive(t *testing.T) { + // empty strings in runOutputs will ignore read timeouts + tests := []testInteractive{ + {runLines: []string{".."}, runError: "1:1 syntax error on '.' at 1:1"}, + {runLines: []string{"1++"}, runError: "1:1 invalid operation"}, + {runLines: []string{"var , b = 1, 2"}, runError: "1:7 syntax error: unexpected ','"}, + + {runLines: []string{"", "1"}, runOutputs: []string{"", "1"}}, + {runLines: []string{"1 + 1"}, runOutputs: []string{"2"}}, + {runLines: []string{"a = 1", "b = 2", "a + b"}, runOutputs: []string{"1", "2", "3"}}, + {runLines: []string{"a = 1", "if a == 1 {", "b = 1", "b = 2", "}", "a"}, runOutputs: []string{"1", "", "", "", "2", "1"}}, + {runLines: []string{"a = 1", "for i = 0; i < 2; i++ {", "a++", "}", "a"}, runOutputs: []string{"1", "", "", "", "3"}}, + {runLines: []string{"1 + 1", "// comment 1", "2 + 2 // comment 2", "// 3 + 3"}, runOutputs: []string{"2", "", "4", ""}}, + } + runInteractiveTests(t, tests) +} + +func runInteractiveTests(t *testing.T, tests []testInteractive) { + // create logger + // Note: logger is used for debugging since real stdout cannot be used + logger = log.New(os.Stderr, "", log.Ldate|log.Ltime|log.LUTC|log.Llongfile) + gopath := os.Getenv("GOPATH") + if gopath == "" { + b, err := exec.Command("go", "env", "GOPATH").CombinedOutput() + if err != nil { + t.Fatal(err) + } + gopath = strings.TrimSpace(string(b)) + } + filehandle, err := os.OpenFile(filepath.Join(gopath, "bin", "anko_test.log"), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + t.Fatal("OpenFile error:", err) + } + defer filehandle.Close() + logger.SetOutput(filehandle) + logger.Print("logger file created") + + // defer sending std back to real + realStdin := os.Stdin + realStderr := os.Stderr + realStdout := os.Stdout + defer setStd(realStdin, realStderr, realStdout) + + // create pipes + readFromIn, writeToIn, err := os.Pipe() + if err != nil { + t.Fatal("Pipe error:", err) + } + os.Stdin = readFromIn + logger.Print("pipe in created") + readFromErr, writeToErr, err := os.Pipe() + if err != nil { + t.Fatal("Pipe error:", err) + } + os.Stderr = writeToErr + logger.Print("pipe err created") + readFromOut, writeToOut, err := os.Pipe() + if err != nil { + t.Fatal("Pipe error:", err) + } + os.Stdout = writeToOut + logger.Print("pipe out created") + + // setup reader + readerErr := bufio.NewReaderSize(readFromErr, 256) + readerOut := bufio.NewReaderSize(readFromOut, 256) + chanQuit := make(chan struct{}, 1) + chanErr := make(chan string, 3) + chanOut := make(chan string, 3) + readTimeout := 10 * time.Millisecond + var dataErr string + var dataOut string + + go readerToChan(t, chanQuit, readerErr, chanErr) + go readerToChan(t, chanQuit, readerOut, chanOut) + + go runInteractive() + + time.Sleep(readTimeout) + + // basic read and write to make sure things are working + _, err = writeToIn.WriteString("1\n") + if err != nil { + t.Fatal("Stdin WriteString error:", err) + } + select { + case dataOut = <-chanOut: + if len(dataOut) > 0 && dataOut[0] == '>' { + dataOut = dataOut[1:] + dataOut = strings.TrimSpace(dataOut) + } + if dataOut != "1" { + t.Fatalf("Stdout - received: %v - expected: %v - basic test", dataOut, "1") + } + case dataErr = <-chanErr: + dataErr = strings.TrimSpace(dataErr) + if dataErr != "" { + t.Fatalf("Stderr - received: %v - expected: %v - basic test", dataErr, "") + } + case <-time.After(readTimeout): + t.Fatal("read timeout for basic test") + } + + // run tests + logger.Print("running tests start") + for _, test := range tests { + + for i, runLine := range test.runLines { + + _, err = writeToIn.WriteString(runLine + "\n") + if err != nil { + t.Fatal("Stdin WriteString error:", err) + } + + select { + case <-time.After(readTimeout): + if test.runOutputs[i] != "" { + t.Fatalf("read timeout for i: %v - runLines: %v", i, test.runLines) + } + case dataOut = <-chanOut: + for len(dataOut) > 0 && dataOut[0] == '>' { + dataOut = dataOut[1:] + dataOut = strings.TrimSpace(dataOut) + } + if dataOut != test.runOutputs[i] { + t.Fatalf("Stdout - received: %v - expected: %v - i: %v - runLines: %v", dataOut, test.runOutputs[i], i, test.runLines) + } + case dataErr = <-chanErr: + dataErr = strings.TrimSpace(dataErr) + if dataErr != test.runError { + t.Fatalf("Stderr - received: %v - expected: %v - i: %v - runLines: %v", dataErr, test.runError, i, test.runLines) + } + + // to clean output from error + _, err = writeToIn.WriteString("1\n") + if err != nil { + t.Fatal("Stdin WriteString error:", err) + } + + select { + case dataOut = <-chanOut: + for len(dataOut) > 0 && dataOut[0] == '>' { + dataOut = dataOut[1:] + dataOut = strings.TrimSpace(dataOut) + } + if dataOut != "1" { + t.Fatalf("Stdout - received: %v - expected: %v - i: %v - runLines: %v", dataOut, test.runOutputs[i], i, test.runLines) + } + case <-time.After(readTimeout): + t.Fatalf("read timeout for i: %v - runLines: %v", i, test.runLines) + } + + } + + } + + } + logger.Print("running tests end") + + // quit + _, err = writeToIn.WriteString("quit()\n") + if err != nil { + t.Fatal("Stdin WriteString error:", err) + } + logger.Print("quit() sent") + + close(chanQuit) + logger.Print("chanQuit closed") + + writeToErr.Close() + writeToOut.Close() + logger.Print("pipes closed") +} + +func readerToChan(t *testing.T, chanQuit chan struct{}, reader *bufio.Reader, chanOut chan string) { + logger.Print("readerToChan start") + for { + data, err := reader.ReadString('\n') + if err != nil && err != io.EOF { + t.Fatal("readerToChan ReadString error:", err) + } + select { + case <-chanQuit: + logger.Print("readerToChan end") + return + default: + } + chanOut <- data + } +} + +func setStd(stdin *os.File, stderr *os.File, stdout *os.File) { + os.Stdin = stdin + os.Stderr = stderr + os.Stdout = stdout +} diff --git a/src/tool/run/ast/ast.go b/src/tool/run/ast/ast.go new file mode 100644 index 0000000..c2f83f7 --- /dev/null +++ b/src/tool/run/ast/ast.go @@ -0,0 +1,38 @@ +package ast + +// Token is used in the lexer to split characters into a string called a token +type Token struct { + PosImpl + Tok int + Lit string +} + +// TypeKind is the kinds of types +type TypeKind int + +const ( + // TypeDefault default type + TypeDefault TypeKind = iota + // TypePtr ptr type + TypePtr + // TypeSlice slice type + TypeSlice + // TypeMap map type + TypeMap + // TypeChan chan type + TypeChan + // TypeStructType struct type + TypeStructType +) + +// TypeStruct is the type and sub-types +type TypeStruct struct { + Kind TypeKind + Env []string + Name string + Dimensions int + SubType *TypeStruct + Key *TypeStruct + StructNames []string + StructTypes []*TypeStruct +} diff --git a/src/tool/run/ast/astutil/walk.go b/src/tool/run/ast/astutil/walk.go new file mode 100644 index 0000000..00fa54e --- /dev/null +++ b/src/tool/run/ast/astutil/walk.go @@ -0,0 +1,281 @@ +// +build !appengine + +package astutil + +import ( + "fmt" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/ast" +) + +// WalkFunc is used in Walk to walk the AST +type WalkFunc func(interface{}) error + +// Walk walks the ASTs associated with a statement list generated by parser.ParseSrc +// each expression and/or statement is passed to the WalkFunc function. +// If the WalkFunc returns an error the walk is aborted and the error is returned +func Walk(stmt ast.Stmt, f WalkFunc) error { + return walkStmt(stmt, f) +} + +func walkStmts(stmts []ast.Stmt, f WalkFunc) error { + for _, stmt := range stmts { + if err := walkStmt(stmt, f); err != nil { + return err + } + } + return nil +} + +func walkExprs(exprs []ast.Expr, f WalkFunc) error { + for _, exp := range exprs { + if err := walkExpr(exp, f); err != nil { + return err + } + } + return nil +} + +func walkStmt(stmt ast.Stmt, f WalkFunc) error { + //short circuit out if there are no functions + if stmt == nil || f == nil { + return nil + } + if err := callFunc(stmt, f); err != nil { + return err + } + switch stmt := stmt.(type) { + case *ast.StmtsStmt: + if err := walkStmts(stmt.Stmts, f); err != nil { + return err + } + case *ast.BreakStmt: + case *ast.ContinueStmt: + case *ast.LetMapItemStmt: + if err := walkExpr(stmt.RHS, f); err != nil { + return err + } + return walkExprs(stmt.LHSS, f) + case *ast.ReturnStmt: + return walkExprs(stmt.Exprs, f) + case *ast.ExprStmt: + return walkExpr(stmt.Expr, f) + case *ast.VarStmt: + return walkExprs(stmt.Exprs, f) + case *ast.LetsStmt: + if err := walkExprs(stmt.RHSS, f); err != nil { + return err + } + return walkExprs(stmt.LHSS, f) + case *ast.IfStmt: + if err := walkExpr(stmt.If, f); err != nil { + return err + } + if err := walkStmt(stmt.Then, f); err != nil { + return err + } + if err := walkStmts(stmt.ElseIf, f); err != nil { + return err + } + if err := walkStmt(stmt.Else, f); err != nil { + return err + } + case *ast.TryStmt: + if err := walkStmt(stmt.Try, f); err != nil { + return err + } + if err := walkStmt(stmt.Catch, f); err != nil { + return err + } + if err := walkStmt(stmt.Finally, f); err != nil { + return err + } + case *ast.LoopStmt: + if err := walkExpr(stmt.Expr, f); err != nil { + return err + } + if err := walkStmt(stmt.Stmt, f); err != nil { + return err + } + case *ast.ForStmt: + if err := walkExpr(stmt.Value, f); err != nil { + return err + } + if err := walkStmt(stmt.Stmt, f); err != nil { + return err + } + case *ast.CForStmt: + if err := walkStmt(stmt.Stmt1, f); err != nil { + return err + } + if err := walkExpr(stmt.Expr2, f); err != nil { + return err + } + if err := walkExpr(stmt.Expr3, f); err != nil { + return err + } + if err := walkStmt(stmt.Stmt, f); err != nil { + return err + } + case *ast.ThrowStmt: + if err := walkExpr(stmt.Expr, f); err != nil { + return err + } + case *ast.ModuleStmt: + if err := walkStmt(stmt.Stmt, f); err != nil { + return err + } + case *ast.SwitchStmt: + if err := walkExpr(stmt.Expr, f); err != nil { + return err + } + for _, switchCaseStmt := range stmt.Cases { + caseStmt := switchCaseStmt.(*ast.SwitchCaseStmt) + if err := walkStmt(caseStmt.Stmt, f); err != nil { + return err + } + } + if err := walkStmt(stmt.Default, f); err != nil { + return err + } + case *ast.GoroutineStmt: + return walkExpr(stmt.Expr, f) + default: + return fmt.Errorf("unknown statement %v", reflect.TypeOf(stmt)) + } + return nil +} + +func walkExpr(expr ast.Expr, f WalkFunc) error { + //short circuit out if there are no functions + if expr == nil || f == nil { + return nil + } + if err := callFunc(expr, f); err != nil { + return err + } + switch expr := expr.(type) { + case *ast.OpExpr: + return walkOperator(expr.Op, f) + case *ast.LenExpr: + case *ast.LiteralExpr: + case *ast.IdentExpr: + case *ast.MemberExpr: + return walkExpr(expr.Expr, f) + case *ast.ItemExpr: + if err := walkExpr(expr.Item, f); err != nil { + return err + } + return walkExpr(expr.Index, f) + case *ast.SliceExpr: + if err := walkExpr(expr.Item, f); err != nil { + return err + } + if err := walkExpr(expr.Begin, f); err != nil { + return err + } + return walkExpr(expr.End, f) + case *ast.ArrayExpr: + return walkExprs(expr.Exprs, f) + case *ast.MapExpr: + for i := range expr.Keys { + if err := walkExpr(expr.Keys[i], f); err != nil { + return err + } + if err := walkExpr(expr.Values[i], f); err != nil { + return err + } + } + case *ast.DerefExpr: + return walkExpr(expr.Expr, f) + case *ast.AddrExpr: + return walkExpr(expr.Expr, f) + case *ast.UnaryExpr: + return walkExpr(expr.Expr, f) + case *ast.ParenExpr: + return walkExpr(expr.SubExpr, f) + case *ast.FuncExpr: + return walkStmt(expr.Stmt, f) + case *ast.LetsExpr: + if err := walkExprs(expr.LHSS, f); err != nil { + return err + } + return walkExprs(expr.RHSS, f) + case *ast.AnonCallExpr: + if err := walkExpr(expr.Expr, f); err != nil { + return err + } + return walkExpr(&ast.CallExpr{Func: reflect.Value{}, SubExprs: expr.SubExprs, VarArg: expr.VarArg, Go: expr.Go}, f) + case *ast.CallExpr: + return walkExprs(expr.SubExprs, f) + case *ast.TernaryOpExpr: + if err := walkExpr(expr.Expr, f); err != nil { + return err + } + if err := walkExpr(expr.LHS, f); err != nil { + return err + } + return walkExpr(expr.RHS, f) + case *ast.ImportExpr: + return walkExpr(expr.Name, f) + case *ast.MakeExpr: + if err := walkExpr(expr.LenExpr, f); err != nil { + return err + } + return walkExpr(expr.CapExpr, f) + case *ast.ChanExpr: + if err := walkExpr(expr.RHS, f); err != nil { + return err + } + return walkExpr(expr.LHS, f) + case *ast.IncludeExpr: + if err := walkExpr(expr.ItemExpr, f); err != nil { + return err + } + return walkExpr(expr.ListExpr, f) + default: + return fmt.Errorf("unknown expression %v", reflect.TypeOf(expr)) + } + return nil +} + +func walkOperator(op ast.Operator, f WalkFunc) error { + //short circuit out if there are no functions + if op == nil || f == nil { + return nil + } + if err := callFunc(op, f); err != nil { + return err + } + switch op := op.(type) { + case *ast.BinaryOperator: + if err := walkExpr(op.LHS, f); err != nil { + return err + } + return walkExpr(op.RHS, f) + case *ast.ComparisonOperator: + if err := walkExpr(op.LHS, f); err != nil { + return err + } + return walkExpr(op.RHS, f) + case *ast.AddOperator: + if err := walkExpr(op.LHS, f); err != nil { + return err + } + return walkExpr(op.RHS, f) + case *ast.MultiplyOperator: + if err := walkExpr(op.LHS, f); err != nil { + return err + } + return walkExpr(op.RHS, f) + } + return nil +} + +func callFunc(x interface{}, f WalkFunc) error { + if x == nil || f == nil { + return nil + } + return f(x) +} diff --git a/src/tool/run/ast/astutil/walk_test.go b/src/tool/run/ast/astutil/walk_test.go new file mode 100644 index 0000000..0b979de --- /dev/null +++ b/src/tool/run/ast/astutil/walk_test.go @@ -0,0 +1,239 @@ +package astutil + +import ( + "errors" + "fmt" + "testing" + + "github.com/surdeus/goblin/src/tool/run/ast" + "github.com/surdeus/goblin/src/tool/run/parser" +) + +const ( + goodSrc string = ` +var fmt = import("fmt") + +a = "1" +b = 2 +m = {} +func testA(arg1, arg2, arg3) { + v, ok = m["foo"] + return "A" + arg1 + arg2 + arg3 +} + +func Main(arg1) { + fmt.Println("enter Main") + b = testA(1, 2, 3) + Tester() + + if b == 0 { + fmt.Println("b is 0") + } else if b == 1 { + fmt.Println("b is 1") + } else { + fmt.Println("b is other") + } + + switch arg1 { + case 0: + fmt.Println("arg0 is 0") + case 1: + fmt.Println("arg0 is 1") + default: + fmt.Println("arg0 is other") + } + + try { + throw "WTF!" + } catch e { + fmt.Println(e) + } + + for n = 0; n < 3; n++ { + if n < 2 { + continue + } + fmt.Println(n) + } + + for n in [1, 2, 3, 4, 5] { + fmt.Println(n) + if n > 3 { + break + } + } + + n = 0 + for n < 3 { + n++ + } + + a = {"foo": "bar"} + a.foo = "baz" + if a["foo"] == "zoo" { + fmt.Println("foo is zoo") + } + fmt.Println(a["foo"] == "zoo" ? "zoo" : "baz") + + c = make(chan int64) + + go func() { + c <- 1 + c <- 2 + c <- 3 + }() + + println(<-c) + println(<-c) + println(<-c) + + v = make([]int, 3) + fmt.Println("sizeof v is ", len(v)) + + x = 1 + y = (&x) + *y = 2 + fmt.Println(x) + + x, y = !x, 2 + fmt.Println(x, y) + + x = new(string) + fmt.Println(x) + + var f = func() { + return "foo" + } + x = f()[0:1] + y = f()[0] + fmt.Println(x == y ? true : false) +} + +func Tester() { + return "YES" +} + +func testLen() { + return len("test") +} + +fmt.Println(Main(1)) +` +) + +func TestWalk(t *testing.T) { + stmts, err := parser.ParseSrc(goodSrc) + if err != nil { + t.Fatal(err) + } + var mainFound bool + var lenFound bool + err = Walk(stmts, func(e interface{}) error { + switch exp := e.(type) { + case *ast.CallExpr: + switch exp.Name { + case `testA`: + if len(exp.SubExprs) != 3 { + return errors.New("invalid parameter count") + } + case `Main`: + if len(exp.SubExprs) != 1 { + return errors.New("invalid parameter count") + } + case `Tester`: + if len(exp.SubExprs) != 0 { + return errors.New("invalid parameter count") + } + } + case *ast.FuncExpr: + if !mainFound && exp.Name == `Main` { + mainFound = true + } else if mainFound && exp.Name == `Main` { + return errors.New("Main redefined") + } + case *ast.LenExpr: + lenFound = true + } + return nil + }) + if err != nil { + t.Fatal(err) + } + if !mainFound { + t.Fatal("Main not found") + } + if !lenFound { + t.Fatal("len not found") + } +} + +func Example_astWalk() { + src := ` +var fmt = import("fmt") + +func TestFunc(arg1, arg2, arg3) { + return (arg1 + arg2) * arg3 +} + +func Main() { + return TestFunc(1, 2, 3) + BuiltinFuncX(1, 2, 3) +} + +fmt.Println(Main()) + ` + stmts, err := parser.ParseSrc(src) + if err != nil { + fmt.Println("ERROR: ", err) + return + } + var mainFound bool + err = Walk(stmts, func(e interface{}) error { + switch e := e.(type) { + case *ast.CallExpr: + //check if the BuiltinFuncX is getting the right number of args + if e.Name == `BuiltinFuncX` && len(e.SubExprs) != 3 { + return errors.New("invalid number of arguments to BuiltinFuncX") + } + case *ast.FuncExpr: + if !mainFound && e.Name == `Main` { + if len(e.Params) != 0 { + return errors.New("Too many args to main") + } + mainFound = true + } else if mainFound && e.Name == `Main` { + return errors.New("Main redefined") + } + } + return nil + }) + if err != nil { + fmt.Println("ERROR: ", err) + return + } + if !mainFound { + fmt.Println("ERROR: Main not found") + } +} + +func TestBadCode(t *testing.T) { + var codes = []string{ + `const 1 = 2`, + `a["foo"] = 2, 3`, + `if a, 2 {}`, + `if break {}`, + `if a { else }`, + `if a { } else foo { }`, + `try a { } else { }`, + `try { } catch 1, 2 { }`, + `throw 1, 2`, + `for.true`, + `switch { var }`, + `case { var }`, + `v, ok = { "foo": "bar" }[const 1]`, + } + for _, code := range codes { + _, err := parser.ParseSrc(code) + if err == nil { + t.Fatalf("code %q should fail", code) + } + } +} diff --git a/src/tool/run/ast/doc.go b/src/tool/run/ast/doc.go new file mode 100644 index 0000000..8781cfc --- /dev/null +++ b/src/tool/run/ast/doc.go @@ -0,0 +1,2 @@ +// Package ast implements abstruct-syntax-tree for anko. +package ast diff --git a/src/tool/run/ast/expr.go b/src/tool/run/ast/expr.go new file mode 100644 index 0000000..939ab8f --- /dev/null +++ b/src/tool/run/ast/expr.go @@ -0,0 +1,187 @@ +package ast + +import ( + "reflect" +) + +// Expr provides all of interfaces for expression. +type Expr interface { + Pos +} + +// ExprImpl provide commonly implementations for Expr. +type ExprImpl struct { + PosImpl // PosImpl provide Pos() function. +} + +// OpExpr provide operator expression. +type OpExpr struct { + ExprImpl + Op Operator +} + +// LiteralExpr provide literal expression. +type LiteralExpr struct { + ExprImpl + Literal reflect.Value +} + +// ArrayExpr provide Array expression. +type ArrayExpr struct { + ExprImpl + Exprs []Expr + TypeData *TypeStruct +} + +// MapExpr provide Map expression. +type MapExpr struct { + ExprImpl + Keys []Expr + Values []Expr + TypeData *TypeStruct +} + +// IdentExpr provide identity expression. +type IdentExpr struct { + ExprImpl + Lit string +} + +// UnaryExpr provide unary minus expression. ex: -1, ^1, ~1. +type UnaryExpr struct { + ExprImpl + Operator string + Expr Expr +} + +// AddrExpr provide referencing address expression. +type AddrExpr struct { + ExprImpl + Expr Expr +} + +// DerefExpr provide dereferencing address expression. +type DerefExpr struct { + ExprImpl + Expr Expr +} + +// ParenExpr provide parent block expression. +type ParenExpr struct { + ExprImpl + SubExpr Expr +} + +// NilCoalescingOpExpr provide if invalid operator expression. +type NilCoalescingOpExpr struct { + ExprImpl + LHS Expr + RHS Expr +} + +// TernaryOpExpr provide ternary operator expression. +type TernaryOpExpr struct { + ExprImpl + Expr Expr + LHS Expr + RHS Expr +} + +// CallExpr provide calling expression. +type CallExpr struct { + ExprImpl + Func reflect.Value + Name string + SubExprs []Expr + VarArg bool + Go bool +} + +// AnonCallExpr provide anonymous calling expression. ex: func(){}(). +type AnonCallExpr struct { + ExprImpl + Expr Expr + SubExprs []Expr + VarArg bool + Go bool +} + +// MemberExpr provide expression to refer member. +type MemberExpr struct { + ExprImpl + Expr Expr + Name string +} + +// ItemExpr provide expression to refer Map/Array item. +type ItemExpr struct { + ExprImpl + Item Expr + Index Expr +} + +// SliceExpr provide expression to refer slice of Array. +type SliceExpr struct { + ExprImpl + Item Expr + Begin Expr + End Expr + Cap Expr +} + +// FuncExpr provide function expression. +type FuncExpr struct { + ExprImpl + Name string + Stmt Stmt + Params []string + VarArg bool +} + +// LetsExpr provide multiple expression of let. +type LetsExpr struct { + ExprImpl + LHSS []Expr + RHSS []Expr +} + +// ChanExpr provide chan expression. +type ChanExpr struct { + ExprImpl + LHS Expr + RHS Expr +} + +// ImportExpr provide expression to import packages. +type ImportExpr struct { + ExprImpl + Name Expr +} + +// MakeExpr provide expression to make instance. +type MakeExpr struct { + ExprImpl + TypeData *TypeStruct + LenExpr Expr + CapExpr Expr +} + +// MakeTypeExpr provide expression to make type. +type MakeTypeExpr struct { + ExprImpl + Name string + Type Expr +} + +// LenExpr provide expression to get length of array, map, etc. +type LenExpr struct { + ExprImpl + Expr Expr +} + +// IncludeExpr provide in expression +type IncludeExpr struct { + ExprImpl + ItemExpr Expr + ListExpr Expr +} diff --git a/src/tool/run/ast/operator.go b/src/tool/run/ast/operator.go new file mode 100644 index 0000000..b370a68 --- /dev/null +++ b/src/tool/run/ast/operator.go @@ -0,0 +1,43 @@ +package ast + +// Operator provides interfaces for operators. +type Operator interface { + Pos +} + +// OperatorImpl provides common implementations for Operator. +type OperatorImpl struct { + PosImpl // PosImpl provide Pos() function. +} + +// BinaryOperator provides binary operation. +type BinaryOperator struct { + OperatorImpl + LHS Expr + Operator string + RHS Expr +} + +// ComparisonOperator provides comparison operation. +type ComparisonOperator struct { + OperatorImpl + LHS Expr + Operator string + RHS Expr +} + +// AddOperator provides add operation. +type AddOperator struct { + OperatorImpl + LHS Expr + Operator string + RHS Expr +} + +// MultiplyOperator provides multiply operation. +type MultiplyOperator struct { + OperatorImpl + LHS Expr + Operator string + RHS Expr +} diff --git a/src/tool/run/ast/pos.go b/src/tool/run/ast/pos.go new file mode 100644 index 0000000..076fdac --- /dev/null +++ b/src/tool/run/ast/pos.go @@ -0,0 +1,28 @@ +package ast + +// Position provides interface to store code locations. +type Position struct { + Line int + Column int +} + +// Pos interface provides two functions to get/set the position for expression or statement. +type Pos interface { + Position() Position + SetPosition(Position) +} + +// PosImpl provides commonly implementations for Pos. +type PosImpl struct { + pos Position +} + +// Position return the position of the expression or statement. +func (x *PosImpl) Position() Position { + return x.pos +} + +// SetPosition is a function to specify position of the expression or statement. +func (x *PosImpl) SetPosition(pos Position) { + x.pos = pos +} diff --git a/src/tool/run/ast/stmt.go b/src/tool/run/ast/stmt.go new file mode 100644 index 0000000..e7a15a8 --- /dev/null +++ b/src/tool/run/ast/stmt.go @@ -0,0 +1,157 @@ +package ast + +// Stmt provides all of interfaces for statement. +type Stmt interface { + Pos +} + +// StmtImpl provide commonly implementations for Stmt.. +type StmtImpl struct { + PosImpl // PosImpl provide Pos() function. +} + +// StmtsStmt provides statements. +type StmtsStmt struct { + StmtImpl + Stmts []Stmt +} + +// ExprStmt provide expression statement. +type ExprStmt struct { + StmtImpl + Expr Expr +} + +// IfStmt provide "if/else" statement. +type IfStmt struct { + StmtImpl + If Expr + Then Stmt + ElseIf []Stmt // This is array of IfStmt + Else Stmt +} + +// TryStmt provide "try/catch/finally" statement. +type TryStmt struct { + StmtImpl + Try Stmt + Var string + Catch Stmt + Finally Stmt +} + +// ForStmt provide "for in" expression statement. +type ForStmt struct { + StmtImpl + Vars []string + Value Expr + Stmt Stmt +} + +// CForStmt provide C-style "for (;;)" expression statement. +type CForStmt struct { + StmtImpl + Stmt1 Stmt + Expr2 Expr + Expr3 Expr + Stmt Stmt +} + +// LoopStmt provide "for expr" expression statement. +type LoopStmt struct { + StmtImpl + Expr Expr + Stmt Stmt +} + +// BreakStmt provide "break" expression statement. +type BreakStmt struct { + StmtImpl +} + +// ContinueStmt provide "continue" expression statement. +type ContinueStmt struct { + StmtImpl +} + +// ReturnStmt provide "return" expression statement. +type ReturnStmt struct { + StmtImpl + Exprs []Expr +} + +// ThrowStmt provide "throw" expression statement. +type ThrowStmt struct { + StmtImpl + Expr Expr +} + +// ModuleStmt provide "module" expression statement. +type ModuleStmt struct { + StmtImpl + Name string + Stmt Stmt +} + +// SwitchStmt provide switch statement. +type SwitchStmt struct { + StmtImpl + Expr Expr + Cases []Stmt + Default Stmt +} + +// SwitchCaseStmt provide switch case statement. +type SwitchCaseStmt struct { + StmtImpl + Exprs []Expr + Stmt Stmt +} + +// VarStmt provide statement to let variables in current scope. +type VarStmt struct { + StmtImpl + Names []string + Exprs []Expr +} + +// LetsStmt provide multiple statement of let. +type LetsStmt struct { + StmtImpl + LHSS []Expr + RHSS []Expr +} + +// LetMapItemStmt provide statement of let for map item. +type LetMapItemStmt struct { + StmtImpl + LHSS []Expr + RHS Expr +} + +// GoroutineStmt provide statement of groutine. +type GoroutineStmt struct { + StmtImpl + Expr Expr +} + +// DeleteStmt provides statement of delete. +type DeleteStmt struct { + ExprImpl + Item Expr + Key Expr +} + +// CloseStmt provides statement of close. +type CloseStmt struct { + StmtImpl + Expr Expr +} + +// ChanStmt provide chan lets statement. +type ChanStmt struct { + ExprImpl + LHS Expr + OkExpr Expr + RHS Expr +} diff --git a/src/tool/run/cmd/anko-package-gen/main.go b/src/tool/run/cmd/anko-package-gen/main.go new file mode 100644 index 0000000..daaf42f --- /dev/null +++ b/src/tool/run/cmd/anko-package-gen/main.go @@ -0,0 +1,122 @@ +package main + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "log" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" +) + +func pkgName(f string) string { + file, err := parser.ParseFile(token.NewFileSet(), f, nil, parser.PackageClauseOnly) + if err != nil || file == nil { + return "" + } + return file.Name.Name +} + +func isGoFile(dir os.FileInfo) bool { + return !dir.IsDir() && + !strings.HasPrefix(dir.Name(), ".") && // ignore .files + filepath.Ext(dir.Name()) == ".go" +} + +func isPkgFile(dir os.FileInfo) bool { + return isGoFile(dir) && !strings.HasSuffix(dir.Name(), "_test.go") // ignore test files +} + +func parseDir(p string) (map[string]*ast.Package, error) { + isGoFile := func(d os.FileInfo) bool { + return !d.IsDir() && !strings.HasSuffix(d.Name(), "_test.go") && !strings.HasPrefix(d.Name(), "example_") + } + + pkgs, err := parser.ParseDir(token.NewFileSet(), p, isGoFile, parser.ParseComments) + if err != nil { + return nil, err + } + return pkgs, nil +} + +func main() { + pkg := "flag" + if len(os.Args) == 2 { + pkg = os.Args[1] + } + b, err := exec.Command("go", "env", "GOROOT").CombinedOutput() + if err != nil { + log.Fatal(err) + } + paths := []string{filepath.Join(strings.TrimSpace(string(b)), "src")} + b, err = exec.Command("go", "env", "GOPATH").CombinedOutput() + if err != nil { + log.Fatal(err) + } + for _, p := range strings.Split(strings.TrimSpace(string(b)), string(filepath.ListSeparator)) { + paths = append(paths, filepath.Join(p, "src")) + } + for _, p := range paths { + pp := filepath.Join(p, pkg) + pkgs, err := parseDir(pp) + if err != nil || len(pkgs) == 0 { + continue + } + names := map[string]bool{} + pn := pkg + for _, pp := range pkgs { + pn = pp.Name + for _, f := range pp.Files { + for _, d := range f.Decls { + switch decl := d.(type) { + case *ast.GenDecl: + for _, spec := range decl.Specs { + if vspec, ok := spec.(*ast.ValueSpec); ok { + for _, n := range vspec.Names { + c := n.Name[0] + if c < 'A' || c > 'Z' { + continue + } + names[n.Name] = true + } + } + } + case *ast.FuncDecl: + if decl.Recv != nil { + continue + } + c := decl.Name.Name[0] + if c < 'A' || c > 'Z' { + continue + } + names[decl.Name.Name] = true + } + } + } + } + keys := []string{} + for k := range names { + keys = append(keys, k) + } + sort.Strings(keys) + fmt.Printf(`// Package %s implements %s interface for anko script. +package %s + +import ( + "%s" +) + +func init() { + Packages["%s"] = map[string]interface{}{ +`, pn, pkg, pn, pkg, pn) + for _, k := range keys { + fmt.Printf(` "%s": %s.%s,`+"\n", k, pn, k) + } + fmt.Println(` } +}`) + } +} diff --git a/src/tool/run/core/core.go b/src/tool/run/core/core.go new file mode 100644 index 0000000..0c8a2aa --- /dev/null +++ b/src/tool/run/core/core.go @@ -0,0 +1,105 @@ +// Package core implements core interface for anko script. +package core + +import ( + "fmt" + "io/ioutil" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" + "github.com/surdeus/goblin/src/tool/run/parser" + "github.com/surdeus/goblin/src/tool/run/vm" +) + +// Import defines core language builtins - keys, range, println, etc. +func Import(e *env.Env) *env.Env { + e.Define("keys", func(v interface{}) []interface{} { + rv := reflect.ValueOf(v) + if rv.Kind() == reflect.Interface { + rv = rv.Elem() + } + mapKeysValue := rv.MapKeys() + mapKeys := make([]interface{}, len(mapKeysValue)) + for i := 0; i < len(mapKeysValue); i++ { + mapKeys[i] = mapKeysValue[i].Interface() + } + return mapKeys + }) + + e.Define("range", func(args ...int64) []int64 { + var start, stop int64 + var step int64 = 1 + + switch len(args) { + case 0: + panic("range expected at least 1 argument, got 0") + case 1: + stop = args[0] + case 2: + start = args[0] + stop = args[1] + case 3: + start = args[0] + stop = args[1] + step = args[2] + if step == 0 { + panic("range argument 3 must not be zero") + } + default: + panic(fmt.Sprintf("range expected at most 3 arguments, got %d", len(args))) + } + + arr := []int64{} + for i := start; (step > 0 && i < stop) || (step < 0 && i > stop); i += step { + arr = append(arr, i) + } + return arr + }) + + e.Define("typeOf", func(v interface{}) string { + return reflect.TypeOf(v).String() + }) + + e.Define("kindOf", func(v interface{}) string { + typeOf := reflect.TypeOf(v) + if typeOf == nil { + return "nil" + } + return typeOf.Kind().String() + }) + + e.Define("defined", func(s string) bool { + _, err := e.Get(s) + return err == nil + }) + + e.Define("load", func(s string) interface{} { + body, err := ioutil.ReadFile(s) + if err != nil { + panic(err) + } + scanner := new(parser.Scanner) + scanner.Init(string(body)) + stmts, err := parser.Parse(scanner) + if err != nil { + if pe, ok := err.(*parser.Error); ok { + pe.Filename = s + panic(pe) + } + panic(err) + } + rv, err := vm.Run(e, nil, stmts) + if err != nil { + panic(err) + } + return rv + }) + + e.Define("print", fmt.Print) + e.Define("println", fmt.Println) + e.Define("printf", fmt.Printf) + + ImportToX(e) + + return e +} diff --git a/src/tool/run/core/testdata/broken.ank b/src/tool/run/core/testdata/broken.ank new file mode 100644 index 0000000..a4f8fd0 --- /dev/null +++ b/src/tool/run/core/testdata/broken.ank @@ -0,0 +1,6 @@ +#!/usr/bin/env + +use strict; +use warnings; + +die "Hey! I'm anko"; diff --git a/src/tool/run/core/testdata/chan.ank b/src/tool/run/core/testdata/chan.ank new file mode 100644 index 0000000..5745440 --- /dev/null +++ b/src/tool/run/core/testdata/chan.ank @@ -0,0 +1,17 @@ + +c = make(chan int64) +r = [] + +go func() { + c <- 1 + c <- 2 + c <- 3 + close(c) +}() + +for a in c { + r += a +} +is([1,2,3], r, "chan") + +nil diff --git a/src/tool/run/core/testdata/core_test.go b/src/tool/run/core/testdata/core_test.go new file mode 100644 index 0000000..31210af --- /dev/null +++ b/src/tool/run/core/testdata/core_test.go @@ -0,0 +1,149 @@ +package core + +import ( + "fmt" + "os" + "reflect" + "strings" + "testing" + + "github.com/surdeus/goblin/src/tool/run/packages" + "github.com/surdeus/goblin/src/tool/run/vm" +) + +var testCoreEnvSetupFunc = func(t *testing.T, env corelib.Env) { Import(env.(*vm.Env)) } + +func init() { + corelib.NewEnv = func() corelib.Env { + return vm.NewEnv() + } +} + +func TestKeys(t *testing.T) { + os.Setenv("ANKO_DEBUG", "1") + tests := []testlib.Test{ + {Script: `a = {}; b = keys(a)`, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": map[interface{}]interface{}{}}}, + {Script: `a = {"a": nil}; b = keys(a)`, RunOutput: []interface{}{"a"}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"a": nil}}}, + {Script: `a = {"a": 1}; b = keys(a)`, RunOutput: []interface{}{"a"}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"a": int64(1)}}}, + } + testlib.Run(t, tests, &testlib.Options{EnvSetupFunc: &testCoreEnvSetupFunc}) +} + +func TestKindOf(t *testing.T) { + os.Setenv("ANKO_DEBUG", "1") + tests := []testlib.Test{ + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": reflect.Value{}}, RunOutput: "struct", Output: map[string]interface{}{"a": reflect.Value{}}}, + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": nil}, RunOutput: "nil", Output: map[string]interface{}{"a": nil}}, + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": true}, RunOutput: "bool", Output: map[string]interface{}{"a": true}}, + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": int32(1)}, RunOutput: "int32", Output: map[string]interface{}{"a": int32(1)}}, + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": int64(1)}, RunOutput: "int64", Output: map[string]interface{}{"a": int64(1)}}, + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": float32(1.1)}, RunOutput: "float32", Output: map[string]interface{}{"a": float32(1.1)}}, + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": float64(1.1)}, RunOutput: "float64", Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": "a"}, RunOutput: "string", Output: map[string]interface{}{"a": "a"}}, + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": 'a'}, RunOutput: "int32", Output: map[string]interface{}{"a": 'a'}}, + + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": interface{}(nil)}, RunOutput: "nil", Output: map[string]interface{}{"a": interface{}(nil)}}, + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": interface{}(true)}, RunOutput: "bool", Output: map[string]interface{}{"a": interface{}(true)}}, + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": interface{}(int32(1))}, RunOutput: "int32", Output: map[string]interface{}{"a": interface{}(int32(1))}}, + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": interface{}(int64(1))}, RunOutput: "int64", Output: map[string]interface{}{"a": interface{}(int64(1))}}, + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": interface{}(float32(1))}, RunOutput: "float32", Output: map[string]interface{}{"a": interface{}(float32(1))}}, + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": interface{}(float64(1))}, RunOutput: "float64", Output: map[string]interface{}{"a": interface{}(float64(1))}}, + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": interface{}("a")}, RunOutput: "string", Output: map[string]interface{}{"a": interface{}("a")}}, + + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": []interface{}{}}, RunOutput: "slice", Output: map[string]interface{}{"a": []interface{}{}}}, + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": []interface{}{nil}}, RunOutput: "slice", Output: map[string]interface{}{"a": []interface{}{nil}}}, + + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": map[string]interface{}{}}, RunOutput: "map", Output: map[string]interface{}{"a": map[string]interface{}{}}}, + {Script: `kindOf(a)`, Input: map[string]interface{}{"a": map[string]interface{}{"b": "b"}}, RunOutput: "map", Output: map[string]interface{}{"a": map[string]interface{}{"b": "b"}}}, + + {Script: `a = make(interface); kindOf(a)`, RunOutput: "nil", Output: map[string]interface{}{"a": interface{}(nil)}}, + } + testlib.Run(t, tests, &testlib.Options{EnvSetupFunc: &testCoreEnvSetupFunc}) +} + +func TestRange(t *testing.T) { + os.Setenv("ANKO_DEBUG", "") + tests := []testlib.Test{ + // 0 arguments + {Script: `range()`, RunError: fmt.Errorf("range expected at least 1 argument, got 0")}, + // 1 arguments(step == 1, start == 0) + {Script: `range(-1)`, RunOutput: []int64{}}, + {Script: `range(0)`, RunOutput: []int64{}}, + {Script: `range(1)`, RunOutput: []int64{0}}, + {Script: `range(2)`, RunOutput: []int64{0, 1}}, + {Script: `range(10)`, RunOutput: []int64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}, + // 2 arguments(step == 1) + {Script: `range(-5,-1)`, RunOutput: []int64{-5, -4, -3, -2}}, + {Script: `range(-1,1)`, RunOutput: []int64{-1, 0}}, + {Script: `range(1,5)`, RunOutput: []int64{1, 2, 3, 4}}, + // 3 arguments + // step == 2 + {Script: `range(-5,-1,2)`, RunOutput: []int64{-5, -3}}, + {Script: `range(1,5,2)`, RunOutput: []int64{1, 3}}, + {Script: `range(-1,5,2)`, RunOutput: []int64{-1, 1, 3}}, + // step < 0 and from small to large + {Script: `range(-5,-1,-1)`, RunOutput: []int64{}}, + {Script: `range(1,5,-1)`, RunOutput: []int64{}}, + {Script: `range(-1,5,-1)`, RunOutput: []int64{}}, + // step < 0 and from large to small + {Script: `range(-1,-5,-1)`, RunOutput: []int64{-1, -2, -3, -4}}, + {Script: `range(5,1,-1)`, RunOutput: []int64{5, 4, 3, 2}}, + {Script: `range(5,-1,-1)`, RunOutput: []int64{5, 4, 3, 2, 1, 0}}, + // 4,5 arguments + {Script: `range(1,5,1,1)`, RunError: fmt.Errorf("range expected at most 3 arguments, got 4")}, + {Script: `range(1,5,1,1,1)`, RunError: fmt.Errorf("range expected at most 3 arguments, got 5")}, + // more 0 test + {Script: `range(0,1,2)`, RunOutput: []int64{0}}, + {Script: `range(1,0,2)`, RunOutput: []int64{}}, + {Script: `range(1,2,0)`, RunError: fmt.Errorf("range argument 3 must not be zero")}, + } + testlib.Run(t, tests, &testlib.Options{EnvSetupFunc: &testCoreEnvSetupFunc}) +} + +func TestLoad(t *testing.T) { + os.Setenv("ANKO_DEBUG", "") + notFoundRunErrorFunc := func(t *testing.T, err error) { + if err == nil || !strings.HasPrefix(err.Error(), "open testdata/not-found.ank:") { + t.Errorf("load not-found.ank failed - received: %v", err) + } + } + tests := []testlib.Test{ + {Script: `load('testdata/test.ank'); X(1)`, RunOutput: int64(2)}, + {Script: `load('testdata/not-found.ank'); X(1)`, RunErrorFunc: ¬FoundRunErrorFunc}, + {Script: `load('testdata/broken.ank'); X(1)`, RunError: fmt.Errorf("syntax error")}, + } + testlib.Run(t, tests, &testlib.Options{EnvSetupFunc: &testCoreEnvSetupFunc}) +} + +func TestAnk(t *testing.T) { + os.Setenv("ANKO_DEBUG", "") + var testEnvSetupFunc = func(t *testing.T, env corelib.Env) { + e := env.(*vm.Env) + Import(e) + packages.DefineImport(e) + } + tests := []testlib.Test{ + {Script: `load('testdata/testing.ank'); load('testdata/let.ank')`}, + {Script: `load('testdata/testing.ank'); load('testdata/toString.ank')`}, + {Script: `load('testdata/testing.ank'); load('testdata/op.ank')`}, + {Script: `load('testdata/testing.ank'); load('testdata/func.ank')`}, + {Script: `load('testdata/testing.ank'); load('testdata/len.ank')`}, + {Script: `load('testdata/testing.ank'); load('testdata/for.ank')`}, + {Script: `load('testdata/testing.ank'); load('testdata/switch.ank')`}, + {Script: `load('testdata/testing.ank'); load('testdata/if.ank')`}, + {Script: `load('testdata/testing.ank'); load('testdata/toBytes.ank')`}, + {Script: `load('testdata/testing.ank'); load('testdata/toRunes.ank')`}, + {Script: `load('testdata/testing.ank'); load('testdata/chan.ank')`}, + } + testlib.Run(t, tests, &testlib.Options{EnvSetupFunc: &testEnvSetupFunc}) +} + +func TestDefined(t *testing.T) { + os.Setenv("ANKO_DEBUG", "") + tests := []testlib.Test{ + {Script: `var a = 1; defined("a")`, RunOutput: true}, + {Script: `defined("a")`, RunOutput: false}, + {Script: `func(){ var a = 1 }(); defined("a")`, RunOutput: false}, + } + testlib.Run(t, tests, &testlib.Options{EnvSetupFunc: &testCoreEnvSetupFunc}) +} diff --git a/src/tool/run/core/testdata/error.ank b/src/tool/run/core/testdata/error.ank new file mode 100644 index 0000000..c222400 --- /dev/null +++ b/src/tool/run/core/testdata/error.ank @@ -0,0 +1,3 @@ +func X(a) { + a.notfound = 1 +} diff --git a/src/tool/run/core/testdata/for.ank b/src/tool/run/core/testdata/for.ank new file mode 100644 index 0000000..9470a6f --- /dev/null +++ b/src/tool/run/core/testdata/for.ank @@ -0,0 +1,75 @@ + +x = 0 +for a in [1,2,3] { + x += 1 +} +is(3, x, "for a in range [1,2,3]") + +x = 0 + +for { + x += 1 + if (x > 3) { + break + } +} +is(4, x, "for loop") + +func loop_with_return_stmt() { + y = 0 + for { + if y == 5 { + return y + } + y++ + } + return 1 +} +is(5, loop_with_return_stmt(), "loop with return stmt") + +func for_with_return_stmt() { + y = 0 + for k in range(0, 10) { + if k == 5 { + return y + } + y++ + } + return 1 +} +is(5, for_with_return_stmt(), "for loop with return stmt") + +x = 0 +for a = 0; a < 10; a++ { + x++ +} +is(10, x, "C-style for loop") + +func cstylefor_with_return_stmt() { + y = 0 + for i = 0; i < 10; i++ { + if i == 5 { + return y + } + y++ + } + + return 1 +} + +is(5, cstylefor_with_return_stmt(), "C-style for loop with return statement") + +resp = { + "items": [{ + "someData": 2, + }] +} + +x = 0 +for item in resp.items { + x += item.someData +} + +is(2, x, "dereference slice element") + +nil diff --git a/src/tool/run/core/testdata/func.ank b/src/tool/run/core/testdata/func.ank new file mode 100644 index 0000000..0d3760a --- /dev/null +++ b/src/tool/run/core/testdata/func.ank @@ -0,0 +1,24 @@ + +func a() { return 2 } +is(2, a(), "func a() { return 2 }") + +func b(x) { return x + 1 } +is(3, b(2), "func b(x) { return x + 1 }") + +func c(x) { return x, x + 1 } +is([2,3], c(2), "func c(x) { return x, x + 1 }") + +func d(x) { return func() { return x + 1 } } +is(3, d(2)(), "func d(x) { return func() { return x + 1 } }") + +var x = func(x) { + return func(y) { + x(y) + } +}(func(z) { + return "Yay! " + z +})("hello world") + +is("Yay! hello world", x, "...") + +nil diff --git a/src/tool/run/core/testdata/if.ank b/src/tool/run/core/testdata/if.ank new file mode 100644 index 0000000..3ae8e56 --- /dev/null +++ b/src/tool/run/core/testdata/if.ank @@ -0,0 +1,14 @@ + +r = -1 +if (false) { + r = 1 +} else if (false) { + r = 2 +} else if (false) { + r = 3 +} else { + r = 4 +} +is(4, r, "if") + +nil diff --git a/src/tool/run/core/testdata/len.ank b/src/tool/run/core/testdata/len.ank new file mode 100644 index 0000000..2d7a0f4 --- /dev/null +++ b/src/tool/run/core/testdata/len.ank @@ -0,0 +1,6 @@ + +is(3, len("foo"), "len(\"foo\")") +is(0, len(""), "len(\"\")") +is(4, len([1,2,true,["foo"]]), "len([1,2,true,[\"foo\"]])") + +nil diff --git a/src/tool/run/core/testdata/let.ank b/src/tool/run/core/testdata/let.ank new file mode 100644 index 0000000..7fee9cf --- /dev/null +++ b/src/tool/run/core/testdata/let.ank @@ -0,0 +1,32 @@ + +a = nil +is(nil, a, "let nil") + +a = 1 +is(1, a, "let int") + +a = 1.2 +is(1.2, a, "let float") + +a = "foo" +is("foo", a, "let string") + +a = nil +is(nil, a, "let nil") + +a = true +is(true, a, "let true") + +a = false +is(false, a, "let false") + +a = [1,2,3] +is([1,2,3], a, "let array") + +a = {"foo": "bar", "bar": "baz"} +is({"bar": "baz", "foo": "bar"}, a, "let map") + +a = {"foo": "bar", "bar": {"blah": true, "blah!": [1.3e3, true]}} +is({"foo": "bar", "bar": {"blah": true, "blah!": [1.3e3, true]}}, a, "let map deep") + +nil diff --git a/src/tool/run/core/testdata/op.ank b/src/tool/run/core/testdata/op.ank new file mode 100644 index 0000000..b59c978 --- /dev/null +++ b/src/tool/run/core/testdata/op.ank @@ -0,0 +1,63 @@ + +ok(1 > 0, "1 > 0") +ok(1 == 1.0, "1 == 1.0") +is(1 != "1", false, "1 != \"1\"") +ok(1 == 1, "1 == 1") +ok(1.1 == 1.1, "1.1 == 1.1") +ok("1" == "1", "\"1\" == \"1\"") + +ok(false != "1", "false != \"1\"") +ok(false != true, "false != true") +ok(false == false, "false == false") +ok(true == true, "true == true") +ok(false == false, "false == false") +ok(nil == nil, "nil == nil") + +ok(1 <= 1, "1 <= 1") +ok(1.0 <= 1.0, "1.0 <= 1.0") + +is(true, 1 <= 2 ? true : false, "1 == 1 ? true : false") + +a = 1; a += 1 +is(2, a, "+=") + +a = 2; a -= 1 +is(1, a, "-=") + +a = 2; a *= 2 +is(4, a, "*=") + +a = 3; a /= 2 +is(1.5, a, "/=") + +a = 2; a++ +is(3, a, "++") + +a = 2; a-- +is(1, a, "--") + +a = 1; a &= 2 +is(0, a, "&=") + +a = 1; a |= 2 +is(3, a, "|=") + +a = !3 +is(false, a, "!3") + +a = !true +is(false, a, "!true") + +a = !false +is(true, a, "!false") + +a = ^3 +is(-4, a, "^3") + +a = 3 << 2 +is(12, a, "3 << 2") + +a = 11 >> 2 +is(2, a, "11 >> 2") + +nil diff --git a/src/tool/run/core/testdata/sort.ank b/src/tool/run/core/testdata/sort.ank new file mode 100644 index 0000000..853f28b --- /dev/null +++ b/src/tool/run/core/testdata/sort.ank @@ -0,0 +1,32 @@ +sort = import("sort") + +a = make([]int) +b = make([]int) +a += [1, 2, 3] +b += [3, 1, 2] +sort.Ints(b) +is(a, b, "sort.Ints") + +a = make([]float64) +b = make([]float64) +a += [1.1, 2.2, 3.3] +b += [3.3, 1.1, 2.2] +sort.Float64s(b) +is(a, b, "sort.Float64s") + +a = make([]string) +b = make([]string) +a += ["a", "b", "c", "d"] +b += ["b", "d", "a", "c"] +sort.Strings(b) +is(a, b, "sort.Strings") + +if go18later() { + a = [1, 3, 2] + sort.Slice(a, func(i, j) { + return a[i] < a[j] + }) + is([1,2,3], a, "sort.Slice") +} + +nil diff --git a/src/tool/run/core/testdata/switch.ank b/src/tool/run/core/testdata/switch.ank new file mode 100644 index 0000000..08423ad --- /dev/null +++ b/src/tool/run/core/testdata/switch.ank @@ -0,0 +1,40 @@ + +x = 0 +r = -1 +switch x { +case 0: + r = 0 +case 1: + r = 1 +case 2: + r = 2 +} +is(0, r, "switch/case") + +x = 3 +r = -1 +switch x { +case 0: + r = 0 +case 1: + r = 1 +case 2: + r = 2 +} +is(-1, r, "switch/case") + +x = 3 +r = -1 +switch x { +case 0: + r = 0 +case 1: + r = 1 +case 2: + r = 2 +default: + r = 3 +} +is(3, r, "switch/default") + +nil diff --git a/src/tool/run/core/testdata/test.ank b/src/tool/run/core/testdata/test.ank new file mode 100644 index 0000000..f96b08d --- /dev/null +++ b/src/tool/run/core/testdata/test.ank @@ -0,0 +1,3 @@ +func X(a) { + return a + 1 +} diff --git a/src/tool/run/core/testdata/testing.ank b/src/tool/run/core/testdata/testing.ank new file mode 100644 index 0000000..cabbf4a --- /dev/null +++ b/src/tool/run/core/testdata/testing.ank @@ -0,0 +1,19 @@ + +var runtime = import("runtime") +var regexp = import("regexp") + +func is(expect, got, name) { + if expect != got { + panic("is - received: " + toString(got) + " - expected: " + toString(expect) + " - for: " + toString(name)) + } +} + +func ok(expect, name) { + if !expect { + panic("ok - received: " + toString(expect) + " - for: " + toString(name)) + } +} + +func go18later() { + return regexp.MustCompile("^go1\\.(1[0-9]|[8-9])\\.[0-9]$").MatchString(runtime.Version()) +} diff --git a/src/tool/run/core/testdata/toBytes.ank b/src/tool/run/core/testdata/toBytes.ank new file mode 100644 index 0000000..8dccfd6 --- /dev/null +++ b/src/tool/run/core/testdata/toBytes.ank @@ -0,0 +1,12 @@ + +a = toByteSlice("あいうえお") +b = [227, 129, 130, 227, 129, 132, 227, 129, 134, 227, 129, 136, 227, 129, 138] +x = 0 +for i = 0; i < len(a); i++ { + if (a[i] == b[i]) { + x++ + } +} +is(x, len(a), "toByteSlice(str)") + +nil diff --git a/src/tool/run/core/testdata/toRunes.ank b/src/tool/run/core/testdata/toRunes.ank new file mode 100644 index 0000000..88a43f8 --- /dev/null +++ b/src/tool/run/core/testdata/toRunes.ank @@ -0,0 +1,12 @@ + +a = toRuneSlice("あいうえお") +b = [12354, 12356, 12358, 12360, 12362] +x = 0 +for i = 0; i < len(a); i++ { + if (a[i] == b[i]) { + x++ + } +} +is(x, len(a), "toRuneSlice(str)") + +nil diff --git a/src/tool/run/core/testdata/toString.ank b/src/tool/run/core/testdata/toString.ank new file mode 100644 index 0000000..31aa217 --- /dev/null +++ b/src/tool/run/core/testdata/toString.ank @@ -0,0 +1,8 @@ + +is("1", toString(1), "toString(int)") +is("1.2", toString(1.2), "toString(float)") +is("true", toString(true), "toString(true)") +is("false", toString(false), "toString(false)") +is("foo", toString("foo"), "toString(\"foo\")") + +nil diff --git a/src/tool/run/core/testdata/toX_test.go b/src/tool/run/core/testdata/toX_test.go new file mode 100644 index 0000000..fd42a5c --- /dev/null +++ b/src/tool/run/core/testdata/toX_test.go @@ -0,0 +1,151 @@ +package core + +import ( + "fmt" + "os" + "testing" + "time" + + "github.com/surdeus/goblin/src/tool/run/internal/testlib" +) + +func TestToX(t *testing.T) { + os.Setenv("ANKO_DEBUG", "1") + tests := []testlib.Test{ + {Script: `toBool(-2)`, RunOutput: false}, + {Script: `toBool(-1.5)`, RunOutput: false}, + {Script: `toBool(-1)`, RunOutput: false}, + {Script: `toBool(-0.4)`, RunOutput: false}, + {Script: `toBool(0)`, RunOutput: false}, + {Script: `toBool(0.4)`, RunOutput: true}, + {Script: `toBool(1)`, RunOutput: true}, + {Script: `toBool(1.5)`, RunOutput: true}, + {Script: `toBool(2)`, RunOutput: true}, + {Script: `toBool(true)`, RunOutput: true}, + {Script: `toBool(false)`, RunOutput: false}, + {Script: `toBool("true")`, RunOutput: true}, + {Script: `toBool("false")`, RunOutput: false}, + {Script: `toBool("yes")`, RunOutput: true}, + {Script: `toBool("ye")`, RunOutput: false}, + {Script: `toBool("y")`, RunOutput: true}, + {Script: `toBool("false")`, RunOutput: false}, + {Script: `toBool("f")`, RunOutput: false}, + {Script: `toBool("")`, RunOutput: false}, + {Script: `toBool(nil)`, RunOutput: false}, + {Script: `toBool({})`, RunOutput: false}, + {Script: `toBool([])`, RunOutput: false}, + {Script: `toBool([true])`, RunOutput: false}, + {Script: `toBool({"true": "true"})`, RunOutput: false}, + {Script: `toString(nil)`, RunOutput: ""}, + {Script: `toString("")`, RunOutput: ""}, + {Script: `toString(1)`, RunOutput: "1"}, + {Script: `toString(1.2)`, RunOutput: "1.2"}, + {Script: `toString(1/3)`, RunOutput: "0.3333333333333333"}, + {Script: `toString(false)`, RunOutput: "false"}, + {Script: `toString(true)`, RunOutput: "true"}, + {Script: `toString({})`, RunOutput: "map[]"}, + {Script: `toString({"foo": "bar"})`, RunOutput: "map[foo:bar]"}, + {Script: `toString([true,nil])`, RunOutput: "[true ]"}, + {Script: `toString(toByteSlice("foo"))`, RunOutput: "foo"}, + {Script: `toInt(nil)`, RunOutput: int64(0)}, + {Script: `toInt(-2)`, RunOutput: int64(-2)}, + {Script: `toInt(-1.4)`, RunOutput: int64(-1)}, + {Script: `toInt(-1)`, RunOutput: int64(-1)}, + {Script: `toInt(0)`, RunOutput: int64(0)}, + {Script: `toInt(1)`, RunOutput: int64(1)}, + {Script: `toInt(1.4)`, RunOutput: int64(1)}, + {Script: `toInt(1.5)`, RunOutput: int64(1)}, + {Script: `toInt(1.9)`, RunOutput: int64(1)}, + {Script: `toInt(2)`, RunOutput: int64(2)}, + {Script: `toInt(2.1)`, RunOutput: int64(2)}, + {Script: `toInt("2")`, RunOutput: int64(2)}, + {Script: `toInt("2.1")`, RunOutput: int64(2)}, + {Script: `toInt(true)`, RunOutput: int64(1)}, + {Script: `toInt(false)`, RunOutput: int64(0)}, + {Script: `toInt({})`, RunOutput: int64(0)}, + {Script: `toInt([])`, RunOutput: int64(0)}, + {Script: `toFloat(nil)`, RunOutput: float64(0.0)}, + {Script: `toFloat(-2)`, RunOutput: float64(-2.0)}, + {Script: `toFloat(-1.4)`, RunOutput: float64(-1.4)}, + {Script: `toFloat(-1)`, RunOutput: float64(-1.0)}, + {Script: `toFloat(0)`, RunOutput: float64(0.0)}, + {Script: `toFloat(1)`, RunOutput: float64(1.0)}, + {Script: `toFloat(1.4)`, RunOutput: float64(1.4)}, + {Script: `toFloat(1.5)`, RunOutput: float64(1.5)}, + {Script: `toFloat(1.9)`, RunOutput: float64(1.9)}, + {Script: `toFloat(2)`, RunOutput: float64(2.0)}, + {Script: `toFloat(2.1)`, RunOutput: float64(2.1)}, + {Script: `toFloat("2")`, RunOutput: float64(2.0)}, + {Script: `toFloat("2.1")`, RunOutput: float64(2.1)}, + {Script: `toFloat(true)`, RunOutput: float64(1.0)}, + {Script: `toFloat(false)`, RunOutput: float64(0.0)}, + {Script: `toFloat({})`, RunOutput: float64(0.0)}, + {Script: `toFloat([])`, RunOutput: float64(0.0)}, + {Script: `toChar(0x1F431)`, RunOutput: "🐱"}, + {Script: `toChar(0)`, RunOutput: "\x00"}, + {Script: `toRune("")`, RunOutput: rune(0)}, + {Script: `toRune("🐱")`, RunOutput: rune(0x1F431)}, + {Script: `toBoolSlice(nil)`, RunOutput: []bool{}}, + {Script: `toBoolSlice(1)`, RunError: fmt.Errorf("function wants argument type []interface {} but received type int64")}, + {Script: `toBoolSlice(1.2)`, RunError: fmt.Errorf("function wants argument type []interface {} but received type float64")}, + {Script: `toBoolSlice(false)`, RunError: fmt.Errorf("function wants argument type []interface {} but received type bool")}, + {Script: `toBoolSlice({})`, RunError: fmt.Errorf("function wants argument type []interface {} but received type map[interface {}]interface {}")}, + {Script: `toBoolSlice([])`, RunOutput: []bool{}}, + {Script: `toBoolSlice([nil])`, RunOutput: []bool{false}}, + {Script: `toBoolSlice([1])`, RunOutput: []bool{false}}, + {Script: `toBoolSlice([1.1])`, RunOutput: []bool{false}}, + {Script: `toBoolSlice([true])`, RunOutput: []bool{true}}, + {Script: `toBoolSlice([[]])`, RunOutput: []bool{false}}, + {Script: `toBoolSlice([{}])`, RunOutput: []bool{false}}, + {Script: `toIntSlice(nil)`, RunOutput: []int64{}}, + {Script: `toIntSlice(1)`, RunError: fmt.Errorf("function wants argument type []interface {} but received type int64")}, + {Script: `toIntSlice(1.2)`, RunError: fmt.Errorf("function wants argument type []interface {} but received type float64")}, + {Script: `toIntSlice(false)`, RunError: fmt.Errorf("function wants argument type []interface {} but received type bool")}, + {Script: `toIntSlice({})`, RunError: fmt.Errorf("function wants argument type []interface {} but received type map[interface {}]interface {}")}, + {Script: `toIntSlice([])`, RunOutput: []int64{}}, + {Script: `toIntSlice([nil])`, RunOutput: []int64{0}}, + {Script: `toIntSlice([1])`, RunOutput: []int64{1}}, + {Script: `toIntSlice([1.1])`, RunOutput: []int64{1}}, + {Script: `toIntSlice([true])`, RunOutput: []int64{0}}, + {Script: `toIntSlice([[]])`, RunOutput: []int64{0}}, + {Script: `toIntSlice([{}])`, RunOutput: []int64{0}}, + {Script: `toFloatSlice(nil)`, RunOutput: []float64{}}, + {Script: `toFloatSlice(1)`, RunError: fmt.Errorf("function wants argument type []interface {} but received type int64")}, + {Script: `toFloatSlice(1.2)`, RunError: fmt.Errorf("function wants argument type []interface {} but received type float64")}, + {Script: `toFloatSlice(false)`, RunError: fmt.Errorf("function wants argument type []interface {} but received type bool")}, + {Script: `toFloatSlice({})`, RunError: fmt.Errorf("function wants argument type []interface {} but received type map[interface {}]interface {}")}, + {Script: `toFloatSlice([])`, RunOutput: []float64{}}, + {Script: `toFloatSlice([nil])`, RunOutput: []float64{0.0}}, + {Script: `toFloatSlice([1])`, RunOutput: []float64{1.0}}, + {Script: `toFloatSlice([1.1])`, RunOutput: []float64{1.1}}, + {Script: `toFloatSlice([true])`, RunOutput: []float64{0.0}}, + {Script: `toFloatSlice([[]])`, RunOutput: []float64{0.0}}, + {Script: `toFloatSlice([{}])`, RunOutput: []float64{0.0}}, + {Script: `toByteSlice(nil)`, RunOutput: []byte{}}, + {Script: `toByteSlice([])`, RunError: fmt.Errorf("function wants argument type string but received type []interface {}")}, + {Script: `toByteSlice(1)`, RunOutput: []byte{0x01}}, // FIXME? + {Script: `toByteSlice(1.1)`, RunError: fmt.Errorf("function wants argument type string but received type float64")}, + {Script: `toByteSlice(true)`, RunError: fmt.Errorf("function wants argument type string but received type bool")}, + {Script: `toByteSlice("foo")`, RunOutput: []byte{'f', 'o', 'o'}}, + {Script: `toByteSlice("世界")`, RunOutput: []byte{0xe4, 0xb8, 0x96, 0xe7, 0x95, 0x8c}}, + {Script: `toRuneSlice(nil)`, RunOutput: []rune{}}, + {Script: `toRuneSlice([])`, RunError: fmt.Errorf("function wants argument type string but received type []interface {}")}, + {Script: `toRuneSlice(1)`, RunOutput: []rune{0x01}}, // FIXME? + {Script: `toRuneSlice(1.1)`, RunError: fmt.Errorf("function wants argument type string but received type float64")}, + {Script: `toRuneSlice(true)`, RunError: fmt.Errorf("function wants argument type string but received type bool")}, + {Script: `toRuneSlice("foo")`, RunOutput: []rune{'f', 'o', 'o'}}, + {Script: `toRuneSlice("世界")`, RunOutput: []rune{'世', '界'}}, + {Script: `toStringSlice([true,false,1])`, RunOutput: []string{"", "", "\x01"}}, // FIXME? + {Script: `toDuration(nil)`, RunOutput: time.Duration(0)}, + {Script: `toDuration(0)`, RunOutput: time.Duration(0)}, + {Script: `toDuration(true)`, RunError: fmt.Errorf("function wants argument type int64 but received type bool")}, + {Script: `toDuration([])`, RunError: fmt.Errorf("function wants argument type int64 but received type []interface {}")}, + {Script: `toDuration({})`, RunError: fmt.Errorf("function wants argument type int64 but received type map[interface {}]interface {}")}, + {Script: `toDuration("")`, RunError: fmt.Errorf("function wants argument type int64 but received type string")}, + {Script: `toDuration("1s")`, RunError: fmt.Errorf("function wants argument type int64 but received type string")}, // TODO + {Script: `toDuration(a)`, Input: map[string]interface{}{"a": int64(time.Duration(123 * time.Minute))}, RunOutput: time.Duration(123 * time.Minute)}, + {Script: `toDuration(a)`, Input: map[string]interface{}{"a": float64(time.Duration(123 * time.Minute))}, RunOutput: time.Duration(123 * time.Minute)}, + {Script: `toDuration(a)`, Input: map[string]interface{}{"a": time.Duration(123 * time.Minute)}, RunOutput: time.Duration(123 * time.Minute)}, + } + testlib.Run(t, tests, &testlib.Options{EnvSetupFunc: &testCoreEnvSetupFunc}) +} diff --git a/src/tool/run/core/toX.go b/src/tool/run/core/toX.go new file mode 100644 index 0000000..0147491 --- /dev/null +++ b/src/tool/run/core/toX.go @@ -0,0 +1,171 @@ +package core + +import ( + "fmt" + "reflect" + "strconv" + "strings" + "time" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +// ImportToX adds all the toX to the env given +func ImportToX(e *env.Env) { + + e.Define("toBool", func(v interface{}) bool { + rv := reflect.ValueOf(v) + if !rv.IsValid() { + return false + } + nt := reflect.TypeOf(true) + if rv.Type().ConvertibleTo(nt) { + return rv.Convert(nt).Bool() + } + if rv.Type().ConvertibleTo(reflect.TypeOf(1.0)) && rv.Convert(reflect.TypeOf(1.0)).Float() > 0.0 { + return true + } + if rv.Kind() == reflect.String { + s := strings.ToLower(v.(string)) + if s == "y" || s == "yes" { + return true + } + b, err := strconv.ParseBool(s) + if err == nil { + return b + } + } + return false + }) + + e.Define("toString", func(v interface{}) string { + if b, ok := v.([]byte); ok { + return string(b) + } + return fmt.Sprint(v) + }) + + e.Define("toInt", func(v interface{}) int64 { + rv := reflect.ValueOf(v) + if !rv.IsValid() { + return 0 + } + nt := reflect.TypeOf(1) + if rv.Type().ConvertibleTo(nt) { + return rv.Convert(nt).Int() + } + if rv.Kind() == reflect.String { + i, err := strconv.ParseInt(v.(string), 10, 64) + if err == nil { + return i + } + f, err := strconv.ParseFloat(v.(string), 64) + if err == nil { + return int64(f) + } + } + if rv.Kind() == reflect.Bool { + if v.(bool) { + return 1 + } + } + return 0 + }) + + e.Define("toFloat", func(v interface{}) float64 { + rv := reflect.ValueOf(v) + if !rv.IsValid() { + return 0 + } + nt := reflect.TypeOf(1.0) + if rv.Type().ConvertibleTo(nt) { + return rv.Convert(nt).Float() + } + if rv.Kind() == reflect.String { + f, err := strconv.ParseFloat(v.(string), 64) + if err == nil { + return f + } + } + if rv.Kind() == reflect.Bool { + if v.(bool) { + return 1.0 + } + } + return 0.0 + }) + + e.Define("toChar", func(s rune) string { + return string(s) + }) + + e.Define("toRune", func(s string) rune { + if len(s) == 0 { + return 0 + } + return []rune(s)[0] + }) + + e.Define("toBoolSlice", func(v []interface{}) []bool { + var result []bool + toSlice(v, &result) + return result + }) + + e.Define("toStringSlice", func(v []interface{}) []string { + var result []string + toSlice(v, &result) + return result + }) + + e.Define("toIntSlice", func(v []interface{}) []int64 { + var result []int64 + toSlice(v, &result) + return result + }) + + e.Define("toFloatSlice", func(v []interface{}) []float64 { + var result []float64 + toSlice(v, &result) + return result + }) + + e.Define("toByteSlice", func(s string) []byte { + return []byte(s) + }) + + e.Define("toRuneSlice", func(s string) []rune { + return []rune(s) + }) + + e.Define("toDuration", func(v int64) time.Duration { + return time.Duration(v) + }) + +} + +// toSlice takes in a "generic" slice and converts and copies +// it's elements into the typed slice pointed at by ptr. +// Note that this is a costly operation. +func toSlice(from []interface{}, ptr interface{}) { + // Value of the pointer to the target + obj := reflect.Indirect(reflect.ValueOf(ptr)) + // We can't just convert from interface{} to whatever the target is (diff memory layout), + // so we need to create a New slice of the proper type and copy the values individually + t := reflect.TypeOf(ptr).Elem() + tt := t.Elem() + slice := reflect.MakeSlice(t, len(from), len(from)) + // Copying the data, val is an addressable Pointer of the actual target type + val := reflect.Indirect(reflect.New(tt)) + for i := 0; i < len(from); i++ { + v := reflect.ValueOf(from[i]) + if v.IsValid() && v.Type().ConvertibleTo(tt) { + val.Set(v.Convert(tt)) + } else { + val.Set(reflect.Zero(tt)) + } + slice.Index(i).Set(val) + } + // Ok now assign our slice to the target pointer + obj.Set(slice) +} diff --git a/src/tool/run/env/env.go b/src/tool/run/env/env.go new file mode 100644 index 0000000..d8faa70 --- /dev/null +++ b/src/tool/run/env/env.go @@ -0,0 +1,187 @@ +package env + +import ( + "bytes" + "errors" + "fmt" + "reflect" + "sync" +) + +type ( + // ExternalLookup for Env external lookup of values and types. + ExternalLookup interface { + Get(string) (reflect.Value, error) + Type(string) (reflect.Type, error) + } + + // Env is the environment needed for a VM to run in. + Env struct { + rwMutex *sync.RWMutex + parent *Env + values map[string]reflect.Value + types map[string]reflect.Type + externalLookup ExternalLookup + } +) + +var ( + // Packages is a where packages can be stored so VM import command can be used to import them. + // reflect.Value must be valid or VM may crash. + // For nil must use NilValue. + Packages = make(map[string]map[string]reflect.Value) + // PackageTypes is a where package types can be stored so VM import command can be used to import them + // reflect.Type must be valid or VM may crash. + // For nil type must use NilType. + PackageTypes = make(map[string]map[string]reflect.Type) + + // NilType is the reflect.type of nil + NilType = reflect.TypeOf(nil) + // NilValue is the reflect.value of nil + NilValue = reflect.New(reflect.TypeOf((*interface{})(nil)).Elem()).Elem() + + basicTypes = map[string]reflect.Type{ + "interface": reflect.ValueOf([]interface{}{int64(1)}).Index(0).Type(), + "bool": reflect.TypeOf(true), + "string": reflect.TypeOf("a"), + "int": reflect.TypeOf(int(1)), + "int32": reflect.TypeOf(int32(1)), + "int64": reflect.TypeOf(int64(1)), + "uint": reflect.TypeOf(uint(1)), + "uint32": reflect.TypeOf(uint32(1)), + "uint64": reflect.TypeOf(uint64(1)), + "byte": reflect.TypeOf(byte(1)), + "rune": reflect.TypeOf('a'), + "float32": reflect.TypeOf(float32(1)), + "float64": reflect.TypeOf(float64(1)), + } + + // ErrSymbolContainsDot symbol contains . + ErrSymbolContainsDot = errors.New("symbol contains '.'") +) + +// NewEnv creates new global scope. +func NewEnv() *Env { + return &Env{ + rwMutex: &sync.RWMutex{}, + values: make(map[string]reflect.Value), + } +} + +// NewEnv creates new child scope. +func (e *Env) NewEnv() *Env { + return &Env{ + rwMutex: &sync.RWMutex{}, + parent: e, + values: make(map[string]reflect.Value), + } +} + +// NewModule creates new child scope and define it as a symbol. +// This is a shortcut for calling e.NewEnv then Define that new Env. +func (e *Env) NewModule(symbol string) (*Env, error) { + module := &Env{ + rwMutex: &sync.RWMutex{}, + parent: e, + values: make(map[string]reflect.Value), + } + return module, e.Define(symbol, module) +} + +// SetExternalLookup sets an external lookup +func (e *Env) SetExternalLookup(externalLookup ExternalLookup) { + e.externalLookup = externalLookup +} + +// String returns string of values and types in current scope. +func (e *Env) String() string { + var buffer bytes.Buffer + e.rwMutex.RLock() + + if e.parent == nil { + buffer.WriteString("No parent\n") + } else { + buffer.WriteString("Has parent\n") + } + + for symbol, value := range e.values { + buffer.WriteString(fmt.Sprintf("%v = %#v\n", symbol, value)) + } + + for symbol, aType := range e.types { + buffer.WriteString(fmt.Sprintf("%v = %v\n", symbol, aType)) + } + + e.rwMutex.RUnlock() + return buffer.String() +} + +// GetEnvFromPath returns Env from path +func (e *Env) GetEnvFromPath(path []string) (*Env, error) { + if len(path) < 1 { + return e, nil + } + + var value reflect.Value + var ok bool + for { + // find starting env + value, ok = e.values[path[0]] + if ok { + e, ok = value.Interface().(*Env) + if ok { + break + } + } + if e.parent == nil { + return nil, fmt.Errorf("no namespace called: %v", path[0]) + } + e = e.parent + } + + for i := 1; i < len(path); i++ { + // find child env + value, ok = e.values[path[i]] + if ok { + e, ok = value.Interface().(*Env) + if ok { + continue + } + } + return nil, fmt.Errorf("no namespace called: %v", path[i]) + } + + return e, nil +} + +// Copy the Env for current scope +func (e *Env) Copy() *Env { + e.rwMutex.RLock() + copy := Env{ + rwMutex: &sync.RWMutex{}, + parent: e.parent, + values: make(map[string]reflect.Value, len(e.values)), + externalLookup: e.externalLookup, + } + for name, value := range e.values { + copy.values[name] = value + } + if e.types != nil { + copy.types = make(map[string]reflect.Type, len(e.types)) + for name, t := range e.types { + copy.types[name] = t + } + } + e.rwMutex.RUnlock() + return © +} + +// DeepCopy the Env for current scope and parent scopes. +// Note that each scope is a consistent snapshot but not the whole. +func (e *Env) DeepCopy() *Env { + e = e.Copy() + if e.parent != nil { + e.parent = e.parent.DeepCopy() + } + return e +} diff --git a/src/tool/run/env/envExternalLookup_test.go b/src/tool/run/env/envExternalLookup_test.go new file mode 100644 index 0000000..f1f16db --- /dev/null +++ b/src/tool/run/env/envExternalLookup_test.go @@ -0,0 +1,235 @@ +package env + +import ( + "fmt" + "reflect" + "strings" + "testing" +) + +type TestExternalLookup struct { + values map[string]reflect.Value + types map[string]reflect.Type +} + +func NewTestExternalLookup() *TestExternalLookup { + return &TestExternalLookup{ + values: make(map[string]reflect.Value), + types: make(map[string]reflect.Type), + } +} + +func (testExternalLookup *TestExternalLookup) SetValue(symbol string, value interface{}) error { + if strings.Contains(symbol, ".") { + return ErrSymbolContainsDot + } + + if value == nil { + testExternalLookup.values[symbol] = NilValue + } else { + testExternalLookup.values[symbol] = reflect.ValueOf(value) + } + + return nil +} + +func (testExternalLookup *TestExternalLookup) Get(symbol string) (reflect.Value, error) { + if value, ok := testExternalLookup.values[symbol]; ok { + return value, nil + } + return NilValue, fmt.Errorf("undefined symbol '%s'", symbol) +} + +func (testExternalLookup *TestExternalLookup) DefineType(symbol string, aType interface{}) error { + if strings.Contains(symbol, ".") { + return ErrSymbolContainsDot + } + + var reflectType reflect.Type + if aType == nil { + reflectType = NilType + } else { + var ok bool + reflectType, ok = reflectType.(reflect.Type) + if !ok { + reflectType = reflect.TypeOf(aType) + } + } + + testExternalLookup.types[symbol] = reflectType + return nil +} + +func (testExternalLookup *TestExternalLookup) Type(symbol string) (reflect.Type, error) { + if value, ok := testExternalLookup.types[symbol]; ok { + return value, nil + } + return NilType, fmt.Errorf("undefined symbol '%s'", symbol) +} + +func TestExternalLookupValueAndGet(t *testing.T) { + var err error + var value interface{} + tests := []struct { + testInfo string + varName string + varDefineValue interface{} + varGetValue interface{} + varKind reflect.Kind + defineError error + getError error + }{ + {testInfo: "nil", varName: "a", varDefineValue: nil, varGetValue: nil, varKind: reflect.Interface}, + {testInfo: "bool", varName: "a", varDefineValue: true, varGetValue: true, varKind: reflect.Bool}, + {testInfo: "int16", varName: "a", varDefineValue: int16(1), varGetValue: int16(1), varKind: reflect.Int16}, + {testInfo: "int32", varName: "a", varDefineValue: int32(1), varGetValue: int32(1), varKind: reflect.Int32}, + {testInfo: "int64", varName: "a", varDefineValue: int64(1), varGetValue: int64(1), varKind: reflect.Int64}, + {testInfo: "uint32", varName: "a", varDefineValue: uint32(1), varGetValue: uint32(1), varKind: reflect.Uint32}, + {testInfo: "uint64", varName: "a", varDefineValue: uint64(1), varGetValue: uint64(1), varKind: reflect.Uint64}, + {testInfo: "float32", varName: "a", varDefineValue: float32(1), varGetValue: float32(1), varKind: reflect.Float32}, + {testInfo: "float64", varName: "a", varDefineValue: float64(1), varGetValue: float64(1), varKind: reflect.Float64}, + {testInfo: "string", varName: "a", varDefineValue: "a", varGetValue: "a", varKind: reflect.String}, + + {testInfo: "string with dot", varName: "a.a", varDefineValue: "a", varGetValue: nil, varKind: reflect.String, defineError: ErrSymbolContainsDot, getError: fmt.Errorf("undefined symbol 'a.a'")}, + {testInfo: "string with quotes", varName: "a", varDefineValue: `"a"`, varGetValue: `"a"`, varKind: reflect.String}, + } + + // ExternalLookup set And get + for _, test := range tests { + testExternalLookup := NewTestExternalLookup() + env := NewEnv() + env.SetExternalLookup(testExternalLookup) + + err = testExternalLookup.SetValue(test.varName, test.varDefineValue) + if err != nil && test.defineError != nil { + if err.Error() != test.defineError.Error() { + t.Errorf("TestExternalLookupValueAndGet %v - SetValue error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + } else if err != test.defineError { + t.Errorf("TestExternalLookupValueAndGet %v - SetValue error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + value, err = env.Get(test.varName) + if err != nil && test.getError != nil { + if err.Error() != test.getError.Error() { + t.Errorf("TestExternalLookupValueAndGet %v - Get error - received: %v - expected: %v", test.testInfo, err, test.getError) + continue + } + } else if err != test.getError { + t.Errorf("TestExternalLookupValueAndGet %v - Get error - received: %v - expected: %v", test.testInfo, err, test.getError) + continue + } + if value != test.varGetValue { + t.Errorf("TestExternalLookupValueAndGet %v - value check - received %#v expected: %#v", test.testInfo, value, test.varGetValue) + } + } +} + +func TestExternalLookupTypeAndGet(t *testing.T) { + var err error + var valueType reflect.Type + tests := []struct { + testInfo string + varName string + varDefineValue interface{} + defineError error + typeError error + }{ + {testInfo: "nil", varName: "a", varDefineValue: nil}, + {testInfo: "bool", varName: "a", varDefineValue: true}, + {testInfo: "int16", varName: "a", varDefineValue: int16(1)}, + {testInfo: "int32", varName: "a", varDefineValue: int32(1)}, + {testInfo: "int64", varName: "a", varDefineValue: int64(1)}, + {testInfo: "uint32", varName: "a", varDefineValue: uint32(1)}, + {testInfo: "uint64", varName: "a", varDefineValue: uint64(1)}, + {testInfo: "float32", varName: "a", varDefineValue: float32(1)}, + {testInfo: "float64", varName: "a", varDefineValue: float64(1)}, + {testInfo: "string", varName: "a", varDefineValue: "a"}, + + {testInfo: "string with dot", varName: "a.a", varDefineValue: nil, defineError: ErrSymbolContainsDot, typeError: fmt.Errorf("undefined type 'a.a'")}, + } + + // DefineType + for _, test := range tests { + testExternalLookup := NewTestExternalLookup() + env := NewEnv() + env.SetExternalLookup(testExternalLookup) + + err = testExternalLookup.DefineType(test.varName, test.varDefineValue) + if err != nil && test.defineError != nil { + if err.Error() != test.defineError.Error() { + t.Errorf("TestExternalLookupTypeAndGet %v - DefineType error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + } else if err != test.defineError { + t.Errorf("TestExternalLookupTypeAndGet %v - DefineType error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + + valueType, err = env.Type(test.varName) + if err != nil && test.typeError != nil { + if err.Error() != test.typeError.Error() { + t.Errorf("TestExternalLookupTypeAndGet %v - Type error - received: %v - expected: %v", test.testInfo, err, test.typeError) + continue + } + } else if err != test.typeError { + t.Errorf("TestExternalLookupTypeAndGet %v - Type error - received: %v - expected: %v", test.testInfo, err, test.typeError) + continue + } + if valueType == nil || test.varDefineValue == nil { + if valueType != reflect.TypeOf(test.varDefineValue) { + t.Errorf("TestExternalLookupTypeAndGet %v - Type check - received: %v - expected: %v", test.testInfo, valueType, reflect.TypeOf(test.varDefineValue)) + } + } else if valueType.String() != reflect.TypeOf(test.varDefineValue).String() { + t.Errorf("TestExternalLookupTypeAndGet %v - Type check - received: %v - expected: %v", test.testInfo, valueType, reflect.TypeOf(test.varDefineValue)) + } + } + +} + +func TestExternalLookupAddr(t *testing.T) { + var err error + tests := []struct { + testInfo string + varName string + varDefineValue interface{} + defineError error + addrError error + }{ + {testInfo: "nil", varName: "a", varDefineValue: nil, addrError: nil}, + {testInfo: "bool", varName: "a", varDefineValue: true, addrError: fmt.Errorf("unaddressable")}, + {testInfo: "int64", varName: "a", varDefineValue: int64(1), addrError: fmt.Errorf("unaddressable")}, + {testInfo: "float64", varName: "a", varDefineValue: float64(1), addrError: fmt.Errorf("unaddressable")}, + {testInfo: "string", varName: "a", varDefineValue: "a", addrError: fmt.Errorf("unaddressable")}, + } + + for _, test := range tests { + envParent := NewEnv() + testExternalLookup := NewTestExternalLookup() + envParent.SetExternalLookup(testExternalLookup) + envChild := envParent.NewEnv() + + err = testExternalLookup.SetValue(test.varName, test.varDefineValue) + if err != nil && test.defineError != nil { + if err.Error() != test.defineError.Error() { + t.Errorf("TestExternalLookupAddr %v - SetValue error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + } else if err != test.defineError { + t.Errorf("TestExternalLookupAddr %v - SetValue error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + + _, err = envChild.Addr(test.varName) + if err != nil && test.addrError != nil { + if err.Error() != test.addrError.Error() { + t.Errorf("TestExternalLookupAddr %v - Addr error - received: %v - expected: %v", test.testInfo, err, test.addrError) + continue + } + } else if err != test.addrError { + t.Errorf("TestExternalLookupAddr %v - Addr error - received: %v - expected: %v", test.testInfo, err, test.addrError) + continue + } + } +} diff --git a/src/tool/run/env/envTypes.go b/src/tool/run/env/envTypes.go new file mode 100644 index 0000000..565b7f1 --- /dev/null +++ b/src/tool/run/env/envTypes.go @@ -0,0 +1,83 @@ +package env + +import ( + "fmt" + "reflect" + "strings" +) + +// DefineType defines type in current scope. +func (e *Env) DefineType(symbol string, aType interface{}) error { + var reflectType reflect.Type + if aType == nil { + reflectType = NilType + } else { + var ok bool + reflectType, ok = aType.(reflect.Type) + if !ok { + reflectType = reflect.TypeOf(aType) + } + } + + return e.DefineReflectType(symbol, reflectType) +} + +// DefineReflectType defines type in current scope. +func (e *Env) DefineReflectType(symbol string, reflectType reflect.Type) error { + if strings.Contains(symbol, ".") { + return ErrSymbolContainsDot + } + + e.rwMutex.Lock() + if e.types == nil { + e.types = make(map[string]reflect.Type) + } + e.types[symbol] = reflectType + e.rwMutex.Unlock() + + return nil +} + +// DefineGlobalType defines type in global scope. +func (e *Env) DefineGlobalType(symbol string, aType interface{}) error { + for e.parent != nil { + e = e.parent + } + return e.DefineType(symbol, aType) +} + +// DefineGlobalReflectType defines type in global scope. +func (e *Env) DefineGlobalReflectType(symbol string, reflectType reflect.Type) error { + for e.parent != nil { + e = e.parent + } + return e.DefineReflectType(symbol, reflectType) +} + +// Type returns reflect type from the scope where symbol is frist found. +func (e *Env) Type(symbol string) (reflect.Type, error) { + e.rwMutex.RLock() + reflectType, ok := e.types[symbol] + e.rwMutex.RUnlock() + if ok { + return reflectType, nil + } + + if e.externalLookup != nil { + var err error + reflectType, err = e.externalLookup.Type(symbol) + if err == nil { + return reflectType, nil + } + } + + if e.parent == nil { + reflectType, ok = basicTypes[symbol] + if ok { + return reflectType, nil + } + return NilType, fmt.Errorf("undefined type '%s'", symbol) + } + + return e.parent.Type(symbol) +} diff --git a/src/tool/run/env/envTypes_test.go b/src/tool/run/env/envTypes_test.go new file mode 100644 index 0000000..050de22 --- /dev/null +++ b/src/tool/run/env/envTypes_test.go @@ -0,0 +1,313 @@ +package env + +import ( + "fmt" + "reflect" + "testing" +) + +func TestBasicType(t *testing.T) { + env := NewEnv() + aType, err := env.Type("string") + if err != nil { + t.Fatalf("Type error - %v", err) + } + if aType != reflect.TypeOf("a") { + t.Errorf("Type - received: %v - expected: %v", aType, reflect.TypeOf("a")) + } + + aType, err = env.Type("int64") + if err != nil { + t.Fatal("Type error:", err) + } + if aType != reflect.TypeOf(int64(1)) { + t.Errorf("Type - received: %v - expected: %v", aType, reflect.TypeOf(int64(1))) + } +} + +func TestDefineType(t *testing.T) { + var err error + var valueType reflect.Type + tests := []struct { + testInfo string + varName string + varDefineValue interface{} + defineError error + typeError error + }{ + {testInfo: "nil", varName: "a", varDefineValue: nil}, + {testInfo: "bool", varName: "a", varDefineValue: true}, + {testInfo: "int16", varName: "a", varDefineValue: int16(1)}, + {testInfo: "int32", varName: "a", varDefineValue: int32(1)}, + {testInfo: "int64", varName: "a", varDefineValue: int64(1)}, + {testInfo: "uint32", varName: "a", varDefineValue: uint32(1)}, + {testInfo: "uint64", varName: "a", varDefineValue: uint64(1)}, + {testInfo: "float32", varName: "a", varDefineValue: float32(1)}, + {testInfo: "float64", varName: "a", varDefineValue: float64(1)}, + {testInfo: "string", varName: "a", varDefineValue: "a"}, + + {testInfo: "string with dot", varName: "a.a", varDefineValue: nil, defineError: ErrSymbolContainsDot, typeError: fmt.Errorf("undefined type 'a.a'")}, + } + + // DefineType + for _, test := range tests { + env := NewEnv() + + err = env.DefineType(test.varName, test.varDefineValue) + if err != nil && test.defineError != nil { + if err.Error() != test.defineError.Error() { + t.Errorf("DefineType %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + } else if err != test.defineError { + t.Errorf("DefineType %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + + valueType, err = env.Type(test.varName) + if err != nil && test.typeError != nil { + if err.Error() != test.typeError.Error() { + t.Errorf("DefineType %v - Type error - received: %v - expected: %v", test.testInfo, err, test.typeError) + continue + } + } else if err != test.typeError { + t.Errorf("DefineType %v - Type error - received: %v - expected: %v", test.testInfo, err, test.typeError) + continue + } + if valueType == nil || test.varDefineValue == nil { + if valueType != reflect.TypeOf(test.varDefineValue) { + t.Errorf("DefineType %v - Type check - received: %v - expected: %v", test.testInfo, valueType, reflect.TypeOf(test.varDefineValue)) + } + } else if valueType.String() != reflect.TypeOf(test.varDefineValue).String() { + t.Errorf("DefineType %v - Type check - received: %v - expected: %v", test.testInfo, valueType, reflect.TypeOf(test.varDefineValue)) + } + } + + // DefineType NewEnv + for _, test := range tests { + envParent := NewEnv() + envChild := envParent.NewEnv() + + err = envParent.DefineType(test.varName, test.varDefineValue) + if err != nil && test.defineError != nil { + if err.Error() != test.defineError.Error() { + t.Errorf("DefineType NewEnv %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + } else if err != test.defineError { + t.Errorf("DefineType NewEnv %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + + valueType, err = envChild.Type(test.varName) + if err != nil && test.typeError != nil { + if err.Error() != test.typeError.Error() { + t.Errorf("DefineType NewEnv %v - Type error - received: %v - expected: %v", test.testInfo, err, test.typeError) + continue + } + } else if err != test.typeError { + t.Errorf("DefineType NewEnv %v - Type error - received: %v - expected: %v", test.testInfo, err, test.typeError) + continue + } + if valueType == nil || test.varDefineValue == nil { + if valueType != reflect.TypeOf(test.varDefineValue) { + t.Errorf("DefineType NewEnv %v - Type check - received: %v - expected: %v", test.testInfo, valueType, reflect.TypeOf(test.varDefineValue)) + } + } else if valueType.String() != reflect.TypeOf(test.varDefineValue).String() { + t.Errorf("DefineType NewEnv %v - Type check - received: %v - expected: %v", test.testInfo, valueType, reflect.TypeOf(test.varDefineValue)) + } + } + + // DefineType NewModule + for _, test := range tests { + envParent := NewEnv() + envChild, err := envParent.NewModule("envChild") + if err != nil { + t.Fatalf("DefineType NewModule %v - NewModule error - received: %v - expected: %v", test.testInfo, err, nil) + } + + err = envParent.DefineType(test.varName, test.varDefineValue) + if err != nil && test.defineError != nil { + if err.Error() != test.defineError.Error() { + t.Errorf("DefineType NewModule %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + } else if err != test.defineError { + t.Errorf("DefineType NewModule %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + + valueType, err = envChild.Type(test.varName) + if err != nil && test.typeError != nil { + if err.Error() != test.typeError.Error() { + t.Errorf("DefineType NewModule %v - Type error - received: %v - expected: %v", test.testInfo, err, test.typeError) + continue + } + } else if err != test.typeError { + t.Errorf("DefineType NewModule %v - Type error - received: %v - expected: %v", test.testInfo, err, test.typeError) + continue + } + if valueType == nil || test.varDefineValue == nil { + if valueType != reflect.TypeOf(test.varDefineValue) { + t.Errorf("DefineType NewModule %v - Type check - received: %v - expected: %v", test.testInfo, valueType, reflect.TypeOf(test.varDefineValue)) + } + } else if valueType.String() != reflect.TypeOf(test.varDefineValue).String() { + t.Errorf("DefineType NewModule %v - Type check - received: %v - expected: %v", test.testInfo, valueType, reflect.TypeOf(test.varDefineValue)) + } + } + + // DefineGlobalType + for _, test := range tests { + envParent := NewEnv() + envChild := envParent.NewEnv() + + err = envChild.DefineGlobalType(test.varName, test.varDefineValue) + if err != nil && test.defineError != nil { + if err.Error() != test.defineError.Error() { + t.Errorf("DefineGlobalType %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + } else if err != test.defineError { + t.Errorf("DefineGlobalType %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + + valueType, err = envParent.Type(test.varName) + if err != nil && test.typeError != nil { + if err.Error() != test.typeError.Error() { + t.Errorf("DefineGlobalType %v - Type error - received: %v - expected: %v", test.testInfo, err, test.typeError) + continue + } + } else if err != test.typeError { + t.Errorf("DefineGlobalType %v - Type error - received: %v - expected: %v", test.testInfo, err, test.typeError) + continue + } + if valueType == nil || test.varDefineValue == nil { + if valueType != reflect.TypeOf(test.varDefineValue) { + t.Errorf("DefineGlobalType %v - Type check - received: %v - expected: %v", test.testInfo, valueType, reflect.TypeOf(test.varDefineValue)) + } + } else if valueType.String() != reflect.TypeOf(test.varDefineValue).String() { + t.Errorf("DefineGlobalType %v - Type check - received: %v - expected: %v", test.testInfo, valueType, reflect.TypeOf(test.varDefineValue)) + } + + valueType, err = envChild.Type(test.varName) + if err != nil && test.typeError != nil { + if err.Error() != test.typeError.Error() { + t.Errorf("DefineGlobalType %v - Type error - received: %v - expected: %v", test.testInfo, err, test.typeError) + continue + } + } else if err != test.typeError { + t.Errorf("DefineGlobalType %v - Type error - received: %v - expected: %v", test.testInfo, err, test.typeError) + continue + } + if valueType == nil || test.varDefineValue == nil { + if valueType != reflect.TypeOf(test.varDefineValue) { + t.Errorf("DefineGlobalType %v - Type check - received: %v - expected: %v", test.testInfo, valueType, reflect.TypeOf(test.varDefineValue)) + } + } else if valueType.String() != reflect.TypeOf(test.varDefineValue).String() { + t.Errorf("DefineGlobalType %v - Type check - received: %v - expected: %v", test.testInfo, valueType, reflect.TypeOf(test.varDefineValue)) + } + } + + // DefineGlobalReflectType + for _, test := range tests { + envParent := NewEnv() + envChild := envParent.NewEnv() + + err = envChild.DefineGlobalReflectType(test.varName, reflect.TypeOf(test.varDefineValue)) + if err != nil && test.defineError != nil { + if err.Error() != test.defineError.Error() { + t.Errorf("DefineGlobalReflectType %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + } else if err != test.defineError { + t.Errorf("DefineGlobalReflectType %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + + valueType, err = envParent.Type(test.varName) + if err != nil && test.typeError != nil { + if err.Error() != test.typeError.Error() { + t.Errorf("DefineGlobalReflectType %v - Type error - received: %v - expected: %v", test.testInfo, err, test.typeError) + continue + } + } else if err != test.typeError { + t.Errorf("DefineGlobalReflectType %v - Type error - received: %v - expected: %v", test.testInfo, err, test.typeError) + continue + } + if valueType == nil || test.varDefineValue == nil { + if valueType != reflect.TypeOf(test.varDefineValue) { + t.Errorf("DefineGlobalReflectType %v - Type check - received: %v - expected: %v", test.testInfo, valueType, reflect.TypeOf(test.varDefineValue)) + } + } else if valueType.String() != reflect.TypeOf(test.varDefineValue).String() { + t.Errorf("DefineGlobalReflectType %v - Type check - received: %v - expected: %v", test.testInfo, valueType, reflect.TypeOf(test.varDefineValue)) + } + + valueType, err = envChild.Type(test.varName) + if err != nil && test.typeError != nil { + if err.Error() != test.typeError.Error() { + t.Errorf("DefineGlobalReflectType %v - Type error - received: %v - expected: %v", test.testInfo, err, test.typeError) + continue + } + } else if err != test.typeError { + t.Errorf("DefineGlobalReflectType %v - Type error - received: %v - expected: %v", test.testInfo, err, test.typeError) + continue + } + if valueType == nil || test.varDefineValue == nil { + if valueType != reflect.TypeOf(test.varDefineValue) { + t.Errorf("DefineGlobalReflectType %v - Type check - received: %v - expected: %v", test.testInfo, valueType, reflect.TypeOf(test.varDefineValue)) + } + } else if valueType.String() != reflect.TypeOf(test.varDefineValue).String() { + t.Errorf("DefineGlobalReflectType %v - Type check - received: %v - expected: %v", test.testInfo, valueType, reflect.TypeOf(test.varDefineValue)) + } + } +} + +func TestDefineTypeFail(t *testing.T) { + var err error + tests := []struct { + testInfo string + varName string + varDefineValue interface{} + defineError error + typeError error + }{ + {testInfo: "nil", varName: "a", varDefineValue: nil, typeError: fmt.Errorf("undefined type 'a'")}, + {testInfo: "bool", varName: "a", varDefineValue: true, typeError: fmt.Errorf("undefined type 'a'")}, + {testInfo: "int16", varName: "a", varDefineValue: int16(1), typeError: fmt.Errorf("undefined type 'a'")}, + {testInfo: "int32", varName: "a", varDefineValue: int32(1), typeError: fmt.Errorf("undefined type 'a'")}, + {testInfo: "int64", varName: "a", varDefineValue: int64(1), typeError: fmt.Errorf("undefined type 'a'")}, + {testInfo: "uint32", varName: "a", varDefineValue: uint32(1), typeError: fmt.Errorf("undefined type 'a'")}, + {testInfo: "uint64", varName: "a", varDefineValue: uint64(1), typeError: fmt.Errorf("undefined type 'a'")}, + {testInfo: "float32", varName: "a", varDefineValue: float32(1), typeError: fmt.Errorf("undefined type 'a'")}, + {testInfo: "float64", varName: "a", varDefineValue: float64(1), typeError: fmt.Errorf("undefined type 'a'")}, + {testInfo: "string", varName: "a", varDefineValue: "a", typeError: fmt.Errorf("undefined type 'a'")}, + } + + // DefineTypeFail + for _, test := range tests { + envParent := NewEnv() + envChild := envParent.NewEnv() + + err = envChild.DefineType(test.varName, test.varDefineValue) + if err != nil && test.defineError != nil { + if err.Error() != test.defineError.Error() { + t.Errorf("TestDefineTypeFail %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + } else if err != test.defineError { + t.Errorf("TestDefineTypeFail %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + + _, err = envParent.Type(test.varName) + if err != nil && test.typeError != nil { + if err.Error() != test.typeError.Error() { + t.Errorf("TestDefineTypeFail %v - Type error - received: %v - expected: %v", test.testInfo, err, test.typeError) + continue + } + } else if err != test.typeError { + t.Errorf("TestDefineTypeFail %v - Type error - received: %v - expected: %v", test.testInfo, err, test.typeError) + } + } +} diff --git a/src/tool/run/env/envValues.go b/src/tool/run/env/envValues.go new file mode 100644 index 0000000..a5da28f --- /dev/null +++ b/src/tool/run/env/envValues.go @@ -0,0 +1,161 @@ +package env + +import ( + "fmt" + "reflect" + "strings" +) + +// define + +// Define defines/sets interface value to symbol in current scope. +func (e *Env) Define(symbol string, value interface{}) error { + if value == nil { + return e.DefineValue(symbol, NilValue) + } + return e.DefineValue(symbol, reflect.ValueOf(value)) +} + +// DefineValue defines/sets reflect value to symbol in current scope. +func (e *Env) DefineValue(symbol string, value reflect.Value) error { + if strings.Contains(symbol, ".") { + return ErrSymbolContainsDot + } + e.rwMutex.Lock() + e.values[symbol] = value + e.rwMutex.Unlock() + + return nil +} + +// DefineGlobal defines/sets interface value to symbol in global scope. +func (e *Env) DefineGlobal(symbol string, value interface{}) error { + for e.parent != nil { + e = e.parent + } + return e.Define(symbol, value) +} + +// DefineGlobalValue defines/sets reflect value to symbol in global scope. +func (e *Env) DefineGlobalValue(symbol string, value reflect.Value) error { + for e.parent != nil { + e = e.parent + } + return e.DefineValue(symbol, value) +} + +// set + +// Set interface value to the scope where symbol is frist found. +func (e *Env) Set(symbol string, value interface{}) error { + if value == nil { + return e.SetValue(symbol, NilValue) + } + return e.SetValue(symbol, reflect.ValueOf(value)) +} + +// SetValue reflect value to the scope where symbol is frist found. +func (e *Env) SetValue(symbol string, value reflect.Value) error { + e.rwMutex.RLock() + _, ok := e.values[symbol] + e.rwMutex.RUnlock() + if ok { + e.rwMutex.Lock() + e.values[symbol] = value + e.rwMutex.Unlock() + return nil + } + + if e.parent == nil { + return fmt.Errorf("undefined symbol '%s'", symbol) + } + return e.parent.SetValue(symbol, value) +} + +// get + +// Get returns interface value from the scope where symbol is frist found. +func (e *Env) Get(symbol string) (interface{}, error) { + rv, err := e.GetValue(symbol) + return rv.Interface(), err +} + +// GetValue returns reflect value from the scope where symbol is frist found. +func (e *Env) GetValue(symbol string) (reflect.Value, error) { + e.rwMutex.RLock() + value, ok := e.values[symbol] + e.rwMutex.RUnlock() + if ok { + return value, nil + } + + if e.externalLookup != nil { + var err error + value, err = e.externalLookup.Get(symbol) + if err == nil { + return value, nil + } + } + + if e.parent == nil { + return NilValue, fmt.Errorf("undefined symbol '%s'", symbol) + } + + return e.parent.GetValue(symbol) +} + +// delete + +// Delete deletes symbol in current scope. +func (e *Env) Delete(symbol string) { + e.rwMutex.Lock() + delete(e.values, symbol) + e.rwMutex.Unlock() +} + +// DeleteGlobal deletes the first matching symbol found in current or parent scope. +func (e *Env) DeleteGlobal(symbol string) { + if e.parent == nil { + e.Delete(symbol) + return + } + + e.rwMutex.RLock() + _, ok := e.values[symbol] + e.rwMutex.RUnlock() + + if ok { + e.Delete(symbol) + return + } + + e.parent.DeleteGlobal(symbol) +} + +// Addr + +// Addr returns reflect.Addr of value for first matching symbol found in current or parent scope. +func (e *Env) Addr(symbol string) (reflect.Value, error) { + e.rwMutex.RLock() + defer e.rwMutex.RUnlock() + + if v, ok := e.values[symbol]; ok { + if v.CanAddr() { + return v.Addr(), nil + } + return NilValue, fmt.Errorf("unaddressable") + } + if e.externalLookup != nil { + v, err := e.externalLookup.Get(symbol) + if err == nil { + if v.CanAddr() { + return v.Addr(), nil + } + return NilValue, fmt.Errorf("unaddressable") + } + } + if e.parent == nil { + return NilValue, fmt.Errorf("undefined symbol '%s'", symbol) + } + return e.parent.Addr(symbol) +} diff --git a/src/tool/run/env/envValues_test.go b/src/tool/run/env/envValues_test.go new file mode 100644 index 0000000..81c9611 --- /dev/null +++ b/src/tool/run/env/envValues_test.go @@ -0,0 +1,787 @@ +package env + +import ( + "fmt" + "reflect" + "sync" + "testing" +) + +func TestSetError(t *testing.T) { + envParent := NewEnv() + envChild := envParent.NewEnv() + err := envChild.Set("a", "a") + if err == nil { + t.Errorf("Set error - received: %v - expected: %v", err, fmt.Errorf("undefined symbol 'a'")) + } else if err.Error() != "undefined symbol 'a'" { + t.Errorf("Set error - received: %v - expected: %v", err, fmt.Errorf("undefined symbol 'a'")) + } +} + +func TestAddrError(t *testing.T) { + envParent := NewEnv() + envChild := envParent.NewEnv() + _, err := envChild.Addr("a") + if err == nil { + t.Errorf("Addr error - received: %v - expected: %v", err, fmt.Errorf("undefined symbol 'a'")) + } else if err.Error() != "undefined symbol 'a'" { + t.Errorf("Addr error - received: %v - expected: %v", err, fmt.Errorf("undefined symbol 'a'")) + } +} + +func TestDefineGlobalValue(t *testing.T) { + envParent := NewEnv() + envChild := envParent.NewEnv() + err := envChild.DefineGlobalValue("a", reflect.ValueOf("a")) + if err != nil { + t.Fatal("DefineGlobalValue error:", err) + } + + var value interface{} + value, err = envParent.Get("a") + if err != nil { + t.Fatal("Get error:", err) + } + v, ok := value.(string) + if !ok { + t.Fatalf("value - received: %T - expected: %T", value, "a") + } + if v != "a" { + t.Fatalf("value - received: %v - expected: %v", v, "a") + } +} + +func TestDefineAndGet(t *testing.T) { + var err error + var value interface{} + tests := []struct { + testInfo string + varName string + varDefineValue interface{} + varGetValue interface{} + varKind reflect.Kind + defineError error + getError error + }{ + {testInfo: "nil", varName: "a", varDefineValue: reflect.Value{}, varGetValue: reflect.Value{}, varKind: reflect.Invalid}, + {testInfo: "nil", varName: "a", varDefineValue: nil, varGetValue: nil, varKind: reflect.Interface}, + {testInfo: "bool", varName: "a", varDefineValue: true, varGetValue: true, varKind: reflect.Bool}, + {testInfo: "int16", varName: "a", varDefineValue: int16(1), varGetValue: int16(1), varKind: reflect.Int16}, + {testInfo: "int32", varName: "a", varDefineValue: int32(1), varGetValue: int32(1), varKind: reflect.Int32}, + {testInfo: "int64", varName: "a", varDefineValue: int64(1), varGetValue: int64(1), varKind: reflect.Int64}, + {testInfo: "uint32", varName: "a", varDefineValue: uint32(1), varGetValue: uint32(1), varKind: reflect.Uint32}, + {testInfo: "uint64", varName: "a", varDefineValue: uint64(1), varGetValue: uint64(1), varKind: reflect.Uint64}, + {testInfo: "float32", varName: "a", varDefineValue: float32(1), varGetValue: float32(1), varKind: reflect.Float32}, + {testInfo: "float64", varName: "a", varDefineValue: float64(1), varGetValue: float64(1), varKind: reflect.Float64}, + {testInfo: "string", varName: "a", varDefineValue: "a", varGetValue: "a", varKind: reflect.String}, + + {testInfo: "string with dot", varName: "a.a", varDefineValue: "a", varGetValue: nil, varKind: reflect.Interface, defineError: ErrSymbolContainsDot, getError: fmt.Errorf("undefined symbol 'a.a'")}, + {testInfo: "string with quotes", varName: "a", varDefineValue: `"a"`, varGetValue: `"a"`, varKind: reflect.String}, + } + + // DefineAndGet + for _, test := range tests { + env := NewEnv() + + err = env.Define(test.varName, test.varDefineValue) + if err != nil && test.defineError != nil { + if err.Error() != test.defineError.Error() { + t.Errorf("DefineAndGet %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + } else if err != test.defineError { + t.Errorf("DefineAndGet %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + + value, err = env.Get(test.varName) + if err != nil && test.getError != nil { + if err.Error() != test.getError.Error() { + t.Errorf("DefineAndGet %v - Get error - received: %v - expected: %v", test.testInfo, err, test.getError) + continue + } + } else if err != test.getError { + t.Errorf("DefineAndGet %v - Get error - received: %v - expected: %v", test.testInfo, err, test.getError) + continue + } + if value != test.varGetValue { + t.Errorf("DefineAndGet %v - value check - received %#v expected: %#v", test.testInfo, value, test.varGetValue) + } + } + + // DefineAndGet NewEnv + for _, test := range tests { + envParent := NewEnv() + envChild := envParent.NewEnv() + + err = envParent.Define(test.varName, test.varDefineValue) + if err != nil && test.defineError != nil { + if err.Error() != test.defineError.Error() { + t.Errorf("DefineAndGet NewEnv %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + } else if err != test.defineError { + t.Errorf("DefineAndGet NewEnv %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + + value, err = envChild.Get(test.varName) + if err != nil && test.getError != nil { + if err.Error() != test.getError.Error() { + t.Errorf("DefineAndGet NewEnv %v - Get error - received: %v - expected: %v", test.testInfo, err, test.getError) + continue + } + } else if err != test.getError { + t.Errorf("DefineAndGet NewEnv %v - Get error - received: %v - expected: %v", test.testInfo, err, test.getError) + continue + } + if value != test.varGetValue { + t.Errorf("DefineAndGet NewEnv %v - value check - received %#v expected: %#v", test.testInfo, value, test.varGetValue) + } + } + + // DefineAndGet DefineGlobal + for _, test := range tests { + envParent := NewEnv() + envChild := envParent.NewEnv() + + err = envChild.DefineGlobal(test.varName, test.varDefineValue) + if err != nil && test.defineError != nil { + if err.Error() != test.defineError.Error() { + t.Errorf("DefineAndGet DefineGlobal %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + } else if err != test.defineError { + t.Errorf("DefineAndGet DefineGlobal %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + + value, err = envParent.Get(test.varName) + if err != nil && test.getError != nil { + if err.Error() != test.getError.Error() { + t.Errorf("DefineAndGet DefineGlobal %v - Get error - received: %v - expected: %v", test.testInfo, err, test.getError) + continue + } + } else if err != test.getError { + t.Errorf("DefineAndGet DefineGlobal %v - Get error - received: %v - expected: %v", test.testInfo, err, test.getError) + continue + } + if value != test.varGetValue { + t.Errorf("DefineAndGet DefineGlobal %v - value check - received %#v expected: %#v", test.testInfo, value, test.varGetValue) + } + } + +} + +func TestDefineModify(t *testing.T) { + var err error + var value interface{} + tests := []struct { + testInfo string + varName string + varDefineValue interface{} + varGetValue interface{} + varKind reflect.Kind + defineError error + getError error + }{ + {testInfo: "nil", varName: "a", varDefineValue: nil, varGetValue: nil, varKind: reflect.Interface}, + {testInfo: "bool", varName: "a", varDefineValue: true, varGetValue: true, varKind: reflect.Bool}, + {testInfo: "int64", varName: "a", varDefineValue: int64(1), varGetValue: int64(1), varKind: reflect.Int64}, + {testInfo: "float64", varName: "a", varDefineValue: float64(1), varGetValue: float64(1), varKind: reflect.Float64}, + {testInfo: "string", varName: "a", varDefineValue: "a", varGetValue: "a", varKind: reflect.String}, + } + changeTests := []struct { + varDefineValue interface{} + varGetValue interface{} + varKind reflect.Kind + defineError error + getError error + }{ + {varDefineValue: nil, varGetValue: nil, varKind: reflect.Interface}, + {varDefineValue: "a", varGetValue: "a", varKind: reflect.String}, + {varDefineValue: int64(1), varGetValue: int64(1), varKind: reflect.Int64}, + {varDefineValue: float64(1), varGetValue: float64(1), varKind: reflect.Float64}, + {varDefineValue: true, varGetValue: true, varKind: reflect.Bool}, + } + + // DefineModify + for _, test := range tests { + env := NewEnv() + + err = env.Define(test.varName, test.varDefineValue) + if err != nil && test.defineError != nil { + if err.Error() != test.defineError.Error() { + t.Errorf("DefineModify %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + } else if err != test.defineError { + t.Errorf("DefineModify %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + + value, err = env.Get(test.varName) + if err != nil && test.getError != nil { + if err.Error() != test.getError.Error() { + t.Errorf("DefineModify %v - Get error - received: %v - expected: %v", test.testInfo, err, test.getError) + continue + } + } else if err != test.getError { + t.Errorf("DefineModify %v - Get error - received: %v - expected: %v", test.testInfo, err, test.getError) + continue + } + if value != test.varGetValue { + t.Errorf("DefineModify %v - value check - received %#v expected: %#v", test.testInfo, value, test.varGetValue) + } + + // DefineModify changeTest + for _, changeTest := range changeTests { + err = env.Set(test.varName, changeTest.varDefineValue) + if err != nil && changeTest.defineError != nil { + if err.Error() != changeTest.defineError.Error() { + t.Errorf("DefineModify changeTest %v - Set error - received: %v - expected: %v", test.testInfo, err, changeTest.defineError) + continue + } + } else if err != changeTest.defineError { + t.Errorf("DefineModify changeTest %v - Set error - received: %v - expected: %v", test.testInfo, err, changeTest.defineError) + continue + } + + value, err = env.Get(test.varName) + if err != nil && changeTest.getError != nil { + if err.Error() != changeTest.getError.Error() { + t.Errorf("DefineModify changeTest %v - Get error - received: %v - expected: %v", test.testInfo, err, changeTest.getError) + continue + } + } else if err != changeTest.getError { + t.Errorf("DefineModify changeTest %v - Get error - received: %v - expected: %v", test.testInfo, err, changeTest.getError) + continue + } + if value != changeTest.varGetValue { + t.Errorf("DefineModify changeTest %v - value check - received %#v expected: %#v", test.testInfo, value, changeTest.varGetValue) + } + } + } + + // DefineModify envParent + for _, test := range tests { + envParent := NewEnv() + envChild := envParent.NewEnv() + + err = envParent.Define(test.varName, test.varDefineValue) + if err != nil && test.defineError != nil { + if err.Error() != test.defineError.Error() { + t.Errorf("DefineModify envParent %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + } else if err != test.defineError { + t.Errorf("DefineModify envParent %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + + value, err = envChild.Get(test.varName) + if err != nil && test.getError != nil { + if err.Error() != test.getError.Error() { + t.Errorf("DefineModify envParent %v - Get error - received: %v - expected: %v", test.testInfo, err, test.getError) + continue + } + } else if err != test.getError { + t.Errorf("DefineModify envParent %v - Get error - received: %v - expected: %v", test.testInfo, err, test.getError) + continue + } + if value != test.varGetValue { + t.Errorf("DefineModify envParent %v - value check - received %#v expected: %#v", test.testInfo, value, test.varGetValue) + } + + for _, changeTest := range changeTests { + err = envParent.Set(test.varName, changeTest.varDefineValue) + if err != nil && changeTest.defineError != nil { + if err.Error() != changeTest.defineError.Error() { + t.Errorf("DefineModify envParent changeTest %v - Set error - received: %v - expected: %v", test.testInfo, err, changeTest.defineError) + continue + } + } else if err != changeTest.defineError { + t.Errorf("DefineModify envParent changeTest %v - Set error - received: %v - expected: %v", test.testInfo, err, changeTest.defineError) + continue + } + + value, err = envChild.Get(test.varName) + if err != nil && changeTest.getError != nil { + if err.Error() != changeTest.getError.Error() { + t.Errorf("DefineModify envParent changeTest %v - Get error - received: %v - expected: %v", test.testInfo, err, changeTest.getError) + continue + } + } else if err != changeTest.getError { + t.Errorf("ChanDefineModify envParent changeTestgeTest %v - Get error - received: %v - expected: %v", test.testInfo, err, changeTest.getError) + continue + } + if value != changeTest.varGetValue { + t.Errorf("DefineModify envParent changeTest %v - value check - received %#v expected: %#v", test.testInfo, value, changeTest.varGetValue) + } + } + } + + // DefineModify envChild + for _, test := range tests { + envParent := NewEnv() + envChild := envParent.NewEnv() + + err = envParent.Define(test.varName, test.varDefineValue) + if err != nil && test.defineError != nil { + if err.Error() != test.defineError.Error() { + t.Errorf("DefineModify envChild %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + } else if err != test.defineError { + t.Errorf("DefineModify envChild %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + + value, err = envChild.Get(test.varName) + if err != nil && test.getError != nil { + if err.Error() != test.getError.Error() { + t.Errorf("DefineModify envChild %v - Get error - received: %v - expected: %v", test.testInfo, err, test.getError) + continue + } + } else if err != test.getError { + t.Errorf("DefineModify envChild %v - Get error - received: %v - expected: %v", test.testInfo, err, test.getError) + continue + } + if value != test.varGetValue { + t.Errorf("DefineModify envChild %v - value check - received %#v expected: %#v", test.testInfo, value, test.varGetValue) + } + + for _, changeTest := range changeTests { + err = envChild.Set(test.varName, changeTest.varDefineValue) + if err != nil && changeTest.defineError != nil { + if err.Error() != changeTest.defineError.Error() { + t.Errorf("DefineModify envChild changeTest %v - Set error - received: %v - expected: %v", test.testInfo, err, changeTest.defineError) + continue + } + } else if err != changeTest.defineError { + t.Errorf("DefineModify envChild changeTest %v - Set error - received: %v - expected: %v", test.testInfo, err, changeTest.defineError) + continue + } + + value, err = envChild.Get(test.varName) + if err != nil && changeTest.getError != nil { + if err.Error() != changeTest.getError.Error() { + t.Errorf("DefineModify envChild changeTest %v - Get error - received: %v - expected: %v", test.testInfo, err, changeTest.getError) + continue + } + } else if err != changeTest.getError { + t.Errorf("ChanDefineModify envChild changeTestgeTest %v - Get error - received: %v - expected: %v", test.testInfo, err, changeTest.getError) + continue + } + if value != changeTest.varGetValue { + t.Errorf("DefineModify envChild changeTest %v - value check - received %#v expected: %#v", test.testInfo, value, changeTest.varGetValue) + } + } + } +} + +func TestAddr(t *testing.T) { + var err error + tests := []struct { + testInfo string + varName string + varDefineValue interface{} + defineError error + addrError error + }{ + {testInfo: "nil", varName: "a", varDefineValue: nil, addrError: nil}, + {testInfo: "string", varName: "a", varDefineValue: "a", addrError: fmt.Errorf("unaddressable")}, + {testInfo: "int64", varName: "a", varDefineValue: int64(1), addrError: fmt.Errorf("unaddressable")}, + {testInfo: "float64", varName: "a", varDefineValue: float64(1), addrError: fmt.Errorf("unaddressable")}, + {testInfo: "bool", varName: "a", varDefineValue: true, addrError: fmt.Errorf("unaddressable")}, + } + + // TestAddr + for _, test := range tests { + envParent := NewEnv() + envChild := envParent.NewEnv() + + err = envParent.Define(test.varName, test.varDefineValue) + if err != nil && test.defineError != nil { + if err.Error() != test.defineError.Error() { + t.Errorf("TestAddr %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + } else if err != test.defineError { + t.Errorf("TestAddr %v - Define error - received: %v - expected: %v", test.testInfo, err, test.defineError) + continue + } + + _, err = envChild.Addr(test.varName) + if err != nil && test.addrError != nil { + if err.Error() != test.addrError.Error() { + t.Errorf("TestAddr %v - Addr error - received: %v - expected: %v", test.testInfo, err, test.addrError) + continue + } + } else if err != test.addrError { + t.Errorf("TestAddr %v - Addr error - received: %v - expected: %v", test.testInfo, err, test.addrError) + continue + } + } +} + +func TestDelete(t *testing.T) { + // empty + env := NewEnv() + env.Delete("a") + + // add & delete a + env.Define("a", "a") + env.Delete("a") + + value, err := env.Get("a") + expectedError := "undefined symbol 'a'" + if err == nil || err.Error() != expectedError { + t.Errorf("Get error - received: %v - expected: %v", err, expectedError) + } + if value != nil { + t.Errorf("Get value - received: %#v - expected: %#v", value, nil) + } +} + +func TestDeleteGlobal(t *testing.T) { + // empty + env := NewEnv() + env.DeleteGlobal("a") + + // add & delete a + env.Define("a", "a") + env.DeleteGlobal("a") + + value, err := env.Get("a") + expectedError := "undefined symbol 'a'" + if err == nil || err.Error() != expectedError { + t.Errorf("Get error - received: %v - expected: %v", err, expectedError) + } + if value != nil { + t.Errorf("Get value - received: %#v - expected: %#v", value, nil) + } + + // parent & child, var in child, delete in parent + envChild := env.NewEnv() + envChild.Define("a", "a") + env.DeleteGlobal("a") + + value, err = envChild.Get("a") + if err != nil { + t.Errorf("Get error - received: %v - expected: %v", err, nil) + } + if value != "a" { + t.Errorf("Get value - received: %#v - expected: %#v", value, "a") + } + + // parent & child, var in child, delete in child + envChild.DeleteGlobal("a") + + value, err = envChild.Get("a") + if err == nil || err.Error() != expectedError { + t.Errorf("Get error - received: %v - expected: %v", err, expectedError) + } + if value != nil { + t.Errorf("Get value - received: %#v - expected: %#v", value, nil) + } + + // parent & child, var in parent, delete in child + env.Define("a", "a") + envChild.DeleteGlobal("a") + + value, err = envChild.Get("a") + if err == nil || err.Error() != expectedError { + t.Errorf("Get error - received: %v - expected: %v", err, expectedError) + } + if value != nil { + t.Errorf("Get value - received: %#v - expected: %#v", value, nil) + } + + // parent & child, var in parent, delete in parent + env.Define("a", "a") + env.DeleteGlobal("a") + + value, err = envChild.Get("a") + if err == nil || err.Error() != expectedError { + t.Errorf("Get error - received: %v - expected: %v", err, expectedError) + } + if value != nil { + t.Errorf("Get value - received: %#v - expected: %#v", value, nil) + } +} + +func TestRaceCreateSameVariable(t *testing.T) { + // Test creating same variable in parallel + + waitChan := make(chan struct{}, 1) + var waitGroup sync.WaitGroup + + env := NewEnv() + + for i := 0; i < 100; i++ { + waitGroup.Add(1) + go func(i int) { + <-waitChan + err := env.Define("a", i) + if err != nil { + t.Errorf("Define error: %v", err) + } + _, err = env.Get("a") + if err != nil { + t.Errorf("Get error: %v", err) + } + waitGroup.Done() + }(i) + } + + close(waitChan) + waitGroup.Wait() + + _, err := env.Get("a") + if err != nil { + t.Errorf("Get error: %v", err) + } +} + +func TestRaceCreateDifferentVariables(t *testing.T) { + // Test creating different variables in parallel + + waitChan := make(chan struct{}, 1) + var waitGroup sync.WaitGroup + + env := NewEnv() + + for i := 0; i < 100; i++ { + waitGroup.Add(1) + go func(i int) { + <-waitChan + err := env.Define(fmt.Sprint(i), i) + if err != nil { + t.Errorf("Define error: %v", err) + } + _, err = env.Get(fmt.Sprint(i)) + if err != nil { + t.Errorf("Get error: %v", err) + } + waitGroup.Done() + }(i) + } + + close(waitChan) + waitGroup.Wait() + + for i := 0; i < 100; i++ { + _, err := env.Get(fmt.Sprint(i)) + if err != nil { + t.Errorf("Get error: %v", err) + } + } +} + +func TestRaceReadDifferentVariables(t *testing.T) { + // Test reading different variables in parallel + + waitChan := make(chan struct{}, 1) + var waitGroup sync.WaitGroup + + env := NewEnv() + + for i := 0; i < 100; i++ { + err := env.Define(fmt.Sprint(i), i) + if err != nil { + t.Errorf("Define error: %v", err) + } + _, err = env.Get(fmt.Sprint(i)) + if err != nil { + t.Errorf("Get error: %v", err) + } + } + + for i := 0; i < 100; i++ { + waitGroup.Add(1) + go func(i int) { + <-waitChan + _, err := env.Get(fmt.Sprint(i)) + if err != nil { + t.Errorf("Get error: %v", err) + } + waitGroup.Done() + }(i) + } + + close(waitChan) + waitGroup.Wait() +} + +func TestRaceSetSameVariable(t *testing.T) { + // Test setting same variable in parallel + + waitChan := make(chan struct{}, 1) + var waitGroup sync.WaitGroup + + env := NewEnv() + + err := env.Define("a", 0) + if err != nil { + t.Errorf("Define error: %v", err) + } + _, err = env.Get("a") + if err != nil { + t.Errorf("Get error: %v", err) + } + + for i := 0; i < 100; i++ { + waitGroup.Add(1) + go func(i int) { + <-waitChan + err := env.Set("a", i) + if err != nil { + t.Errorf("Set error: %v", err) + } + waitGroup.Done() + }(i) + } + + close(waitChan) + waitGroup.Wait() + + _, err = env.Get("a") + if err != nil { + t.Errorf("Get error: %v", err) + } +} + +func TestRaceSetSameVariableNewEnv(t *testing.T) { + // Test setting same variable in parallel with NewEnv + + waitChan := make(chan struct{}, 1) + var waitGroup sync.WaitGroup + + env := NewEnv() + + err := env.Define("a", 0) + if err != nil { + t.Errorf("Define error: %v", err) + } + _, err = env.Get("a") + if err != nil { + t.Errorf("Get error: %v", err) + } + + for i := 0; i < 100; i++ { + waitGroup.Add(1) + go func(i int) { + <-waitChan + env = env.NewEnv().NewEnv() + err := env.Set("a", i) + if err != nil { + t.Errorf("Set error: %v", err) + } + waitGroup.Done() + }(i) + } +} + +func TestRaceDefineAndSetSameVariable(t *testing.T) { + // Test defining and setting same variable in parallel + for i := 0; i < 100; i++ { + raceDefineAndSetSameVariable(t) + } +} + +func raceDefineAndSetSameVariable(t *testing.T) { + waitChan := make(chan struct{}, 1) + var waitGroup sync.WaitGroup + + envParent := NewEnv() + envChild := envParent.NewEnv() + + for i := 0; i < 2; i++ { + waitGroup.Add(1) + go func() { + <-waitChan + err := envParent.Set("a", 1) + if err != nil && err.Error() != "undefined symbol 'a'" { + t.Errorf("Set error: %v", err) + } + waitGroup.Done() + }() + waitGroup.Add(1) + go func() { + <-waitChan + err := envParent.Define("a", 2) + if err != nil { + t.Errorf("Define error: %v", err) + } + waitGroup.Done() + }() + waitGroup.Add(1) + go func() { + <-waitChan + err := envChild.Set("a", 3) + if err != nil && err.Error() != "undefined symbol 'a'" { + t.Errorf("Set error: %v", err) + } + waitGroup.Done() + }() + waitGroup.Add(1) + go func() { + <-waitChan + err := envChild.Define("a", 4) + if err != nil { + t.Errorf("Define error: %v", err) + } + waitGroup.Done() + }() + } + + close(waitChan) + waitGroup.Wait() + + _, err := envParent.Get("a") // value of a could be 1, 2, or 3 + if err != nil { + t.Errorf("Get error: %v", err) + } + _, err = envChild.Get("a") // value of a could be 3 or 4 + if err != nil { + t.Errorf("Get error: %v", err) + } +} + +func BenchmarkDefine(b *testing.B) { + var err error + env := NewEnv() + b.ResetTimer() + for i := 0; i < b.N; i++ { + err := env.Define("a", 1) + if err != nil { + b.Errorf("Set error: %v", err) + } + } + b.StopTimer() + _, err = env.Get("a") + if err != nil { + b.Errorf("Get error: %v", err) + } +} + +func BenchmarkSet(b *testing.B) { + env := NewEnv() + err := env.Define("a", 1) + if err != nil { + b.Errorf("Define error: %v", err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + err = env.Set("a", 1) + if err != nil { + b.Errorf("Set error: %v", err) + } + } + b.StopTimer() + _, err = env.Get("a") + if err != nil { + b.Errorf("Get error: %v", err) + } +} diff --git a/src/tool/run/env/env_test.go b/src/tool/run/env/env_test.go new file mode 100644 index 0000000..bd7a682 --- /dev/null +++ b/src/tool/run/env/env_test.go @@ -0,0 +1,394 @@ +package env + +import ( + "reflect" + "testing" +) + +func TestString(t *testing.T) { + t.Parallel() + + env := NewEnv() + env.Define("a", "a") + output := env.String() + expected := `No parent +a = "a" +` + if output != expected { + t.Errorf("received: %v - expected: %v", output, expected) + } + + env = env.NewEnv() + env.Define("b", "b") + output = env.String() + expected = `Has parent +b = "b" +` + if output != expected { + t.Errorf("received: %v - expected: %v", output, expected) + } + + env = NewEnv() + env.Define("c", "c") + env.DefineType("string", "a") + output = env.String() + expected = `No parent +c = "c" +string = string +` + if output != expected { + t.Errorf("received: %v - expected: %v", output, expected) + } +} + +func TestGetEnvFromPath(t *testing.T) { + t.Parallel() + + env := NewEnv() + a, err := env.NewModule("a") + if err != nil { + t.Fatal("NewModule error:", err) + } + var b *Env + b, err = a.NewModule("b") + if err != nil { + t.Fatal("NewModule error:", err) + } + var c *Env + c, err = b.NewModule("c") + if err != nil { + t.Fatal("NewModule error:", err) + } + err = c.Define("d", "d") + if err != nil { + t.Fatal("Define error:", err) + } + + e, err := env.GetEnvFromPath(nil) + if err != nil { + t.Fatal("GetEnvFromPath error:", err) + } + if e == nil { + t.Fatal("GetEnvFromPath e nil") + } + + e, err = env.GetEnvFromPath([]string{}) + if err != nil { + t.Fatal("GetEnvFromPath error:", err) + } + if e == nil { + t.Fatal("GetEnvFromPath e nil") + } + + e, err = env.GetEnvFromPath([]string{"a", "c"}) + expected := "no namespace called: c" + if err == nil || err.Error() != expected { + t.Fatalf("GetEnvFromPath error - received: %v - expected: %v", err, expected) + } + if e != nil { + t.Fatal("GetEnvFromPath e not nil") + } + + // a.b.c + + e, err = env.GetEnvFromPath([]string{"a", "b", "c"}) + if err != nil { + t.Fatal("GetEnvFromPath error:", err) + } + if e == nil { + t.Fatal("GetEnvFromPath e nil") + } + var value interface{} + value, err = e.Get("d") + if err != nil { + t.Fatal("Get error:", err) + } + v, ok := value.(string) + if !ok { + t.Fatal("value not string") + } + if v != "d" { + t.Errorf("value - received: %v - expected: %v", v, "d") + } + + e, err = a.GetEnvFromPath([]string{"a", "b", "c"}) + if err != nil { + t.Fatal("GetEnvFromPath error:", err) + } + if e == nil { + t.Fatal("GetEnvFromPath e nil") + } + value, err = e.Get("d") + if err != nil { + t.Fatal("Get error:", err) + } + v, ok = value.(string) + if !ok { + t.Fatal("value not string") + } + if v != "d" { + t.Errorf("value - received: %v - expected: %v", v, "d") + } + + e, err = b.GetEnvFromPath([]string{"a", "b", "c"}) + if err != nil { + t.Fatal("GetEnvFromPath error:", err) + } + if e == nil { + t.Fatal("GetEnvFromPath e nil") + } + value, err = e.Get("d") + if err != nil { + t.Fatal("Get error:", err) + } + v, ok = value.(string) + if !ok { + t.Fatal("value not string") + } + if v != "d" { + t.Errorf("value - received: %v - expected: %v", v, "d") + } + + e, err = c.GetEnvFromPath([]string{"a", "b", "c"}) + if err != nil { + t.Fatal("GetEnvFromPath error:", err) + } + if e == nil { + t.Fatal("GetEnvFromPath e nil") + } + value, err = e.Get("d") + if err != nil { + t.Fatal("Get error:", err) + } + v, ok = value.(string) + if !ok { + t.Fatal("value not string") + } + if v != "d" { + t.Errorf("value - received: %v - expected: %v", v, "d") + } + + // b.c + + e, err = env.GetEnvFromPath([]string{"b", "c"}) + expected = "no namespace called: b" + if err == nil || err.Error() != expected { + t.Fatalf("GetEnvFromPath error - received: %v - expected: %v", err, expected) + } + if e != nil { + t.Fatal("GetEnvFromPath e not nil") + } + + e, err = a.GetEnvFromPath([]string{"b", "c"}) + if err != nil { + t.Fatal("GetEnvFromPath error:", err) + } + if e == nil { + t.Fatal("GetEnvFromPath e nil") + } + value, err = e.Get("d") + if err != nil { + t.Fatal("Get error:", err) + } + v, ok = value.(string) + if !ok { + t.Fatal("value not string") + } + if v != "d" { + t.Errorf("value - received: %v - expected: %v", v, "d") + } + + e, err = b.GetEnvFromPath([]string{"b", "c"}) + if err != nil { + t.Fatal("GetEnvFromPath error:", err) + } + if e == nil { + t.Fatal("GetEnvFromPath e nil") + } + value, err = e.Get("d") + if err != nil { + t.Fatal("Get error:", err) + } + v, ok = value.(string) + if !ok { + t.Fatal("value not string") + } + if v != "d" { + t.Errorf("value - received: %v - expected: %v", v, "d") + } + + e, err = c.GetEnvFromPath([]string{"b", "c"}) + if err != nil { + t.Fatal("GetEnvFromPath error:", err) + } + if e == nil { + t.Fatal("GetEnvFromPath e nil") + } + value, err = e.Get("d") + if err != nil { + t.Fatal("Get error:", err) + } + v, ok = value.(string) + if !ok { + t.Fatal("value not string") + } + if v != "d" { + t.Errorf("value - received: %v - expected: %v", v, "d") + } + + // c + + e, err = env.GetEnvFromPath([]string{"c"}) + expected = "no namespace called: c" + if err == nil || err.Error() != expected { + t.Fatalf("GetEnvFromPath error - received: %v - expected: %v", err, expected) + } + if e != nil { + t.Fatal("GetEnvFromPath e not nil") + } + + e, err = b.GetEnvFromPath([]string{"c"}) + if err != nil { + t.Fatal("GetEnvFromPath error:", err) + } + if e == nil { + t.Fatal("GetEnvFromPath e nil") + } + value, err = e.Get("d") + if err != nil { + t.Fatal("Get error:", err) + } + v, ok = value.(string) + if !ok { + t.Fatal("value not string") + } + if v != "d" { + t.Errorf("value - received: %v - expected: %v", v, "d") + } + + e, err = c.GetEnvFromPath(nil) + if err != nil { + t.Fatal("GetEnvFromPath error:", err) + } + if e == nil { + t.Fatal("GetEnvFromPath e nil") + } + value, err = e.Get("d") + if err != nil { + t.Fatal("Get error:", err) + } + v, ok = value.(string) + if !ok { + t.Fatal("value not string") + } + if v != "d" { + t.Errorf("value - received: %v - expected: %v", v, "d") + } +} + +func TestCopy(t *testing.T) { + t.Parallel() + + parent := NewEnv() + parent.Define("a", "a") + parent.DefineType("b", []bool{}) + child := parent.NewEnv() + child.Define("c", "c") + child.DefineType("d", []int64{}) + copy := child.Copy() + + if v, e := copy.Get("a"); e != nil || v != "a" { + t.Errorf("copy missing value") + } + if v, e := copy.Type("b"); e != nil || v != reflect.TypeOf([]bool{}) { + t.Errorf("copy missing type") + } + if v, e := copy.Get("c"); e != nil || v != "c" { + t.Errorf("copy missing value") + } + if v, e := copy.Type("d"); e != nil || v != reflect.TypeOf([]int64{}) { + t.Errorf("copy missing type") + } + + // TODO: add more get type tests + + copy.Set("a", "i") + if v, e := child.Get("a"); e != nil || v != "i" { + t.Errorf("parent was not modified") + } + if v, e := copy.Get("a"); e != nil || v != "i" { + t.Errorf("copy did not get parent value") + } + + copy.Set("c", "j") + if v, e := child.Get("c"); e != nil || v != "c" { + t.Errorf("child was not modified") + } + if v, e := copy.Get("c"); e != nil || v != "j" { + t.Errorf("copy child was not modified") + } + + child.Set("a", "x") + if v, e := child.Get("a"); e != nil || v != "x" { + t.Errorf("parent was not modified") + } + if v, e := copy.Get("a"); e != nil || v != "x" { + t.Errorf("copy did not get parent value") + } + + child.Set("c", "z") + if v, e := child.Get("c"); e != nil || v != "z" { + t.Errorf("child was not modified") + } + if v, e := copy.Get("c"); e != nil || v != "j" { + t.Errorf("copy child was modified") + } + + parent.Set("a", "m") + if v, e := child.Get("a"); e != nil || v != "m" { + t.Errorf("parent was not modified") + } + if v, e := copy.Get("a"); e != nil || v != "m" { + t.Errorf("copy did not get parent value") + } + + parent.Define("x", "n") + if v, e := child.Get("x"); e != nil || v != "n" { + t.Errorf("child did not get parent value") + } + if v, e := copy.Get("x"); e != nil || v != "n" { + t.Errorf("copy did not get parent value") + } +} + +func TestDeepCopy(t *testing.T) { + t.Parallel() + + parent := NewEnv() + parent.Define("a", "a") + env := parent.NewEnv() + copy := env.DeepCopy() + + // TODO: add more/better tests, like above + if v, e := copy.Get("a"); e != nil || v != "a" { + t.Errorf("copy doesn't retain original values") + } + parent.Set("a", "b") + if v, e := env.Get("a"); e != nil || v != "b" { + t.Errorf("son was not modified") + } + if v, e := copy.Get("a"); e != nil || v != "a" { + t.Errorf("copy got the new value") + } + parent.Set("a", "c") + if v, e := env.Get("a"); e != nil || v != "c" { + t.Errorf("original was not modified") + } + if v, e := copy.Get("a"); e != nil || v != "a" { + t.Errorf("copy was modified") + } + parent.Define("b", "b") + if _, e := copy.Get("b"); e == nil { + t.Errorf("copy parent was modified") + } +} diff --git a/src/tool/run/misc/vim/ftdetect/ank.vim b/src/tool/run/misc/vim/ftdetect/ank.vim new file mode 100644 index 0000000..13bd684 --- /dev/null +++ b/src/tool/run/misc/vim/ftdetect/ank.vim @@ -0,0 +1 @@ +au BufNewFile,BufRead *.ank setlocal filetype=anko diff --git a/src/tool/run/misc/vim/ftplugin/anko/comment.vim b/src/tool/run/misc/vim/ftplugin/anko/comment.vim new file mode 100644 index 0000000..35f52f9 --- /dev/null +++ b/src/tool/run/misc/vim/ftplugin/anko/comment.vim @@ -0,0 +1,11 @@ +if exists("b:did_ftplugin") + finish +endif +let b:did_ftplugin = 1 + +setlocal comments=s1:# +setlocal commentstring=#\ %s + +let b:undo_ftplugin = "setl com< cms<" + +" vim:ts=4:sw=4:et diff --git a/src/tool/run/misc/vim/ftplugin/anko/play.vim b/src/tool/run/misc/vim/ftplugin/anko/play.vim new file mode 100644 index 0000000..6da8751 --- /dev/null +++ b/src/tool/run/misc/vim/ftplugin/anko/play.vim @@ -0,0 +1,15 @@ +scriptencoding utf-8 + +function! s:play() + let code = join(getline(1, '$'), "\n") + let res = webapi#http#post("http://play-anko.appspot.com/api/play", {"code": code}) + if res.status == "200" + echo iconv(res.content, "utf-8", &encoding) + else + for line in split(res.content, "\n") + echohl Error | echomsg iconv(line, "utf-8", &encoding) | echohl None + endfor + endif +endfunction + +command! -buffer PlayAnko call s:play() diff --git a/src/tool/run/misc/vim/syntax/anko.vim b/src/tool/run/misc/vim/syntax/anko.vim new file mode 100644 index 0000000..9be5c83 --- /dev/null +++ b/src/tool/run/misc/vim/syntax/anko.vim @@ -0,0 +1,100 @@ +if exists("b:current_syntax") + finish +endif + +syn case match + +syn keyword ankoDirective module +syn keyword ankoDeclaration var + +hi def link ankoDirective Statement +hi def link ankoDeclaration Type + +syn keyword ankoStatement return break continue throw +syn keyword ankoConditional if else switch try catch finally +syn keyword ankoLabel case default +syn keyword ankoRepeat for range + +hi def link ankoStatement Statement +hi def link ankoConditional Conditional +hi def link ankoLabel Label +hi def link ankoRepeat Repeat + +syn match ankoDeclaration /\/ +syn match ankoDeclaration /^func\>/ + +syn keyword ankoCast bytes runes string + +hi def link ankoCast Type + +syn keyword ankoBuiltins keys len +syn keyword ankoBuiltins println printf print +syn keyword ankoConstants true false nil + +hi def link ankoBuiltins Keyword +hi def link ankoConstants Keyword + +" Comments; their contents +syn keyword ankoTodo contained TODO FIXME XXX BUG +syn cluster ankoCommentGroup contains=ankoTodo +syn region ankoComment start="#" end="$" contains=@ankoCommentGroup,@Spell + +hi def link ankoComment Comment +hi def link ankoTodo Todo + +" anko escapes +syn match ankoEscapeOctal display contained "\\[0-7]\{3}" +syn match ankoEscapeC display contained +\\[abfnrtv\\'"]+ +syn match ankoEscapeX display contained "\\x\x\{2}" +syn match ankoEscapeU display contained "\\u\x\{4}" +syn match ankoEscapeBigU display contained "\\U\x\{8}" +syn match ankoEscapeError display contained +\\[^0-7xuUabfnrtv\\'"]+ + +hi def link ankoEscapeOctal ankoSpecialString +hi def link ankoEscapeC ankoSpecialString +hi def link ankoEscapeX ankoSpecialString +hi def link ankoEscapeU ankoSpecialString +hi def link ankoEscapeBigU ankoSpecialString +hi def link ankoSpecialString Special +hi def link ankoEscapeError Error + +" Strings and their contents +syn cluster ankoStringGroup contains=ankoEscapeOctal,ankoEscapeC,ankoEscapeX,ankoEscapeU,ankoEscapeBigU,ankoEscapeError +syn region ankoString start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=@ankoStringGroup +syn region ankoRawString start=+`+ end=+`+ + +hi def link ankoString String +hi def link ankoRawString String + +" Characters; their contents +syn cluster ankoCharacterGroup contains=ankoEscapeOctal,ankoEscapeC,ankoEscapeX,ankoEscapeU,ankoEscapeBigU +syn region ankoCharacter start=+'+ skip=+\\\\\|\\'+ end=+'+ contains=@ankoCharacterGroup + +hi def link ankoCharacter Character + +" Regions +syn region ankoBlock start="{" end="}" transparent fold +syn region ankoParen start='(' end=')' transparent + +" Integers +syn match ankoDecimalInt "\<\d\+\([Ee]\d\+\)\?\>" +syn match ankoHexadecimalInt "\<0x\x\+\>" +syn match ankoOctalInt "\<0\o\+\>" +syn match ankoOctalError "\<0\o*[89]\d*\>" + +hi def link ankoDecimalInt Integer +hi def link ankoHexadecimalInt Integer +hi def link ankoOctalInt Integer +hi def link Integer Number + +" Floating point +syn match ankoFloat "\<\d\+\.\d*\([Ee][-+]\d\+\)\?\>" +syn match ankoFloat "\<\.\d\+\([Ee][-+]\d\+\)\?\>" +syn match ankoFloat "\<\d\+[Ee][-+]\d\+\>" + +hi def link ankoFloat Float +hi def link ankoImaginary Number + +syn sync minlines=500 + +let b:current_syntax = "anko" diff --git a/src/tool/run/misc/wasm/anko.go b/src/tool/run/misc/wasm/anko.go new file mode 100644 index 0000000..944d93a --- /dev/null +++ b/src/tool/run/misc/wasm/anko.go @@ -0,0 +1,129 @@ +// +build js,wasm + +package main + +import ( + "fmt" + "html" + "strings" + "syscall/js" + + "github.com/surdeus/goblin/src/tool/run/core" + "github.com/surdeus/goblin/src/tool/run/packages" + "github.com/surdeus/goblin/src/tool/run/parser" + "github.com/surdeus/goblin/src/tool/run/vm" +) + +var ( + result = js.Global.Get("document").Call("getElementById", "result") + input = js.Global.Get("document").Call("getElementById", "input") +) + +func writeCommand(s string) { + result.Set("innerHTML", result.Get("innerHTML").String()+"

"+html.EscapeString(s)+"

") + result.Set("scrollTop", result.Get("scrollHeight").Int()) +} + +func writeStdout(s string) { + result.Set("innerHTML", result.Get("innerHTML").String()+"

"+html.EscapeString(s)+"

") + result.Set("scrollTop", result.Get("scrollHeight").Int()) +} + +func writeStderr(s string) { + result.Set("innerHTML", result.Get("innerHTML").String()+"

"+html.EscapeString(s)+"

") + result.Set("scrollTop", result.Get("scrollHeight").Int()) +} + +func main() { + env := vm.NewEnv() + core.Import(env) + packages.DefineImport(env) + + env.Define("print", func(a ...interface{}) { + writeStdout(fmt.Sprint(a...)) + }) + env.Define("printf", func(a string, b ...interface{}) { + writeStdout(fmt.Sprintf(a, b...)) + }) + + var following bool + var source string + + parser.EnableErrorVerbose() + + ch := make(chan string) + + input.Call("addEventListener", "keypress", js.NewCallback(func(args []js.Value) { + e := args[0] + if e.Get("keyCode").Int() != 13 { + return + } + s := e.Get("target").Get("value").String() + e.Get("target").Set("value", "") + writeCommand(s) + ch <- s + })) + input.Set("disabled", false) + result.Set("innerHTML", "") + + go func() { + for { + text, ok := <-ch + if !ok { + break + } + source += text + if source == "" { + continue + } + if source == "quit()" { + break + } + + stmts, err := parser.ParseSrc(source) + + if e, ok := err.(*parser.Error); ok { + es := e.Error() + if strings.HasPrefix(es, "syntax error: unexpected") { + if strings.HasPrefix(es, "syntax error: unexpected $end,") { + following = true + continue + } + } else { + if e.Pos.Column == len(source) && !e.Fatal { + writeStderr(e.Error()) + following = true + continue + } + if e.Error() == "unexpected EOF" { + following = true + continue + } + } + } + + following = false + source = "" + var v interface{} + + if err == nil { + v, err = vm.Run(stmts, env) + } + if err != nil { + if e, ok := err.(*vm.Error); ok { + writeStderr(fmt.Sprintf("%d:%d %s\n", e.Pos.Line, e.Pos.Column, err)) + } else if e, ok := err.(*parser.Error); ok { + writeStderr(fmt.Sprintf("%d:%d %s\n", e.Pos.Line, e.Pos.Column, err)) + } else { + writeStderr(err.Error()) + } + continue + } + + writeStdout(fmt.Sprintf("%#v\n", v)) + } + }() + + select {} + +} diff --git a/src/tool/run/misc/wasm/index.html b/src/tool/run/misc/wasm/index.html new file mode 100644 index 0000000..87c08ac --- /dev/null +++ b/src/tool/run/misc/wasm/index.html @@ -0,0 +1,35 @@ + + + Anko WebAssembly + + + + + +
loading...
+
+ + diff --git a/src/tool/run/misc/wasm/wasm_exec.js b/src/tool/run/misc/wasm/wasm_exec.js new file mode 100644 index 0000000..fc995ec --- /dev/null +++ b/src/tool/run/misc/wasm/wasm_exec.js @@ -0,0 +1,381 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +(() => { + // Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API). + const isNodeJS = typeof process !== "undefined"; + if (isNodeJS) { + global.require = require; + global.fs = require("fs"); + + const nodeCrypto = require("crypto"); + global.crypto = { + getRandomValues(b) { + nodeCrypto.randomFillSync(b); + }, + }; + + global.performance = { + now() { + const [sec, nsec] = process.hrtime(); + return sec * 1000 + nsec / 1000000; + }, + }; + + const util = require("util"); + global.TextEncoder = util.TextEncoder; + global.TextDecoder = util.TextDecoder; + } else { + window.global = window; + + let outputBuf = ""; + global.fs = { + constants: {}, + writeSync(fd, buf) { + outputBuf += decoder.decode(buf); + const nl = outputBuf.lastIndexOf("\n"); + if (nl != -1) { + console.log(outputBuf.substr(0, nl)); + outputBuf = outputBuf.substr(nl + 1); + } + return buf.length; + }, + openSync(path, flags, mode) { + const err = new Error("not implemented"); + err.code = "ENOSYS"; + throw err; + }, + }; + } + + const encoder = new TextEncoder("utf-8"); + const decoder = new TextDecoder("utf-8"); + + global.Go = class { + constructor() { + this.argv = ["js"]; + this.env = {}; + this.exit = (code) => { + if (code !== 0) { + console.warn("exit code:", code); + } + }; + this._callbackTimeouts = new Map(); + this._nextCallbackTimeoutID = 1; + + const mem = () => { + // The buffer may change when requesting more memory. + return new DataView(this._inst.exports.mem.buffer); + } + + const setInt64 = (addr, v) => { + mem().setUint32(addr + 0, v, true); + mem().setUint32(addr + 4, Math.floor(v / 4294967296), true); + } + + const getInt64 = (addr) => { + const low = mem().getUint32(addr + 0, true); + const high = mem().getInt32(addr + 4, true); + return low + high * 4294967296; + } + + const loadValue = (addr) => { + const id = mem().getUint32(addr, true); + return this._values[id]; + } + + const storeValue = (addr, v) => { + if (v === undefined) { + mem().setUint32(addr, 0, true); + return; + } + if (v === null) { + mem().setUint32(addr, 1, true); + return; + } + this._values.push(v); + mem().setUint32(addr, this._values.length - 1, true); + } + + const loadSlice = (addr) => { + const array = getInt64(addr + 0); + const len = getInt64(addr + 8); + return new Uint8Array(this._inst.exports.mem.buffer, array, len); + } + + const loadSliceOfValues = (addr) => { + const array = getInt64(addr + 0); + const len = getInt64(addr + 8); + const a = new Array(len); + for (let i = 0; i < len; i++) { + const id = mem().getUint32(array + i * 4, true); + a[i] = this._values[id]; + } + return a; + } + + const loadString = (addr) => { + const saddr = getInt64(addr + 0); + const len = getInt64(addr + 8); + return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); + } + + const timeOrigin = Date.now() - performance.now(); + this.importObject = { + go: { + // func wasmExit(code int32) + "runtime.wasmExit": (sp) => { + this.exited = true; + this.exit(mem().getInt32(sp + 8, true)); + }, + + // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) + "runtime.wasmWrite": (sp) => { + const fd = getInt64(sp + 8); + const p = getInt64(sp + 16); + const n = mem().getInt32(sp + 24, true); + fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); + }, + + // func nanotime() int64 + "runtime.nanotime": (sp) => { + setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); + }, + + // func walltime() (sec int64, nsec int32) + "runtime.walltime": (sp) => { + const msec = (new Date).getTime(); + setInt64(sp + 8, msec / 1000); + mem().setInt32(sp + 16, (msec % 1000) * 1000000, true); + }, + + // func scheduleCallback(delay int64) int32 + "runtime.scheduleCallback": (sp) => { + const id = this._nextCallbackTimeoutID; + this._nextCallbackTimeoutID++; + this._callbackTimeouts.set(id, setTimeout( + () => { this._resolveCallbackPromise(); }, + getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early + )); + mem().setInt32(sp + 16, id, true); + }, + + // func clearScheduledCallback(id int32) + "runtime.clearScheduledCallback": (sp) => { + const id = mem().getInt32(sp + 8, true); + clearTimeout(this._callbackTimeouts.get(id)); + this._callbackTimeouts.delete(id); + }, + + // func getRandomData(r []byte) + "runtime.getRandomData": (sp) => { + crypto.getRandomValues(loadSlice(sp + 8)); + }, + + // func boolVal(value bool) ref + "syscall/js.boolVal": (sp) => { + storeValue(sp + 16, mem().getUint8(sp + 8) !== 0); + }, + + // func intVal(value int) ref + "syscall/js.intVal": (sp) => { + storeValue(sp + 16, getInt64(sp + 8)); + }, + + // func floatVal(value float64) ref + "syscall/js.floatVal": (sp) => { + storeValue(sp + 16, mem().getFloat64(sp + 8, true)); + }, + + // func stringVal(value string) ref + "syscall/js.stringVal": (sp) => { + storeValue(sp + 24, loadString(sp + 8)); + }, + + // func valueGet(v ref, p string) ref + "syscall/js.valueGet": (sp) => { + storeValue(sp + 32, Reflect.get(loadValue(sp + 8), loadString(sp + 16))); + }, + + // func valueSet(v ref, p string, x ref) + "syscall/js.valueSet": (sp) => { + Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); + }, + + // func valueIndex(v ref, i int) ref + "syscall/js.valueIndex": (sp) => { + storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); + }, + + // valueSetIndex(v ref, i int, x ref) + "syscall/js.valueSetIndex": (sp) => { + Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); + }, + + // func valueCall(v ref, m string, args []ref) (ref, bool) + "syscall/js.valueCall": (sp) => { + try { + const v = loadValue(sp + 8); + const m = Reflect.get(v, loadString(sp + 16)); + const args = loadSliceOfValues(sp + 32); + storeValue(sp + 56, Reflect.apply(m, v, args)); + mem().setUint8(sp + 60, 1); + } catch (err) { + storeValue(sp + 56, err); + mem().setUint8(sp + 60, 0); + } + }, + + // func valueInvoke(v ref, args []ref) (ref, bool) + "syscall/js.valueInvoke": (sp) => { + try { + const v = loadValue(sp + 8); + const args = loadSliceOfValues(sp + 16); + storeValue(sp + 40, Reflect.apply(v, undefined, args)); + mem().setUint8(sp + 44, 1); + } catch (err) { + storeValue(sp + 40, err); + mem().setUint8(sp + 44, 0); + } + }, + + // func valueNew(v ref, args []ref) (ref, bool) + "syscall/js.valueNew": (sp) => { + try { + const v = loadValue(sp + 8); + const args = loadSliceOfValues(sp + 16); + storeValue(sp + 40, Reflect.construct(v, args)); + mem().setUint8(sp + 44, 1); + } catch (err) { + storeValue(sp + 40, err); + mem().setUint8(sp + 44, 0); + } + }, + + // func valueFloat(v ref) float64 + "syscall/js.valueFloat": (sp) => { + mem().setFloat64(sp + 16, parseFloat(loadValue(sp + 8)), true); + }, + + // func valueInt(v ref) int + "syscall/js.valueInt": (sp) => { + setInt64(sp + 16, parseInt(loadValue(sp + 8))); + }, + + // func valueBool(v ref) bool + "syscall/js.valueBool": (sp) => { + mem().setUint8(sp + 16, !!loadValue(sp + 8)); + }, + + // func valueLength(v ref) int + "syscall/js.valueLength": (sp) => { + setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); + }, + + // valuePrepareString(v ref) (ref, int) + "syscall/js.valuePrepareString": (sp) => { + const str = encoder.encode(String(loadValue(sp + 8))); + storeValue(sp + 16, str); + setInt64(sp + 24, str.length); + }, + + // valueLoadString(v ref, b []byte) + "syscall/js.valueLoadString": (sp) => { + const str = loadValue(sp + 8); + loadSlice(sp + 16).set(str); + }, + + "debug": (value) => { + console.log(value); + }, + } + }; + } + + async run(instance) { + this._inst = instance; + this._values = [ // TODO: garbage collection + undefined, + null, + global, + this._inst.exports.mem, + () => { // resolveCallbackPromise + if (this.exited) { + throw new Error("bad callback: Go program has already exited"); + } + setTimeout(this._resolveCallbackPromise, 0); // make sure it is asynchronous + }, + ]; + this.exited = false; + + const mem = new DataView(this._inst.exports.mem.buffer) + + // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. + let offset = 4096; + + const strPtr = (str) => { + let ptr = offset; + new Uint8Array(mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0")); + offset += str.length + (8 - (str.length % 8)); + return ptr; + }; + + const argc = this.argv.length; + + const argvPtrs = []; + this.argv.forEach((arg) => { + argvPtrs.push(strPtr(arg)); + }); + + const keys = Object.keys(this.env).sort(); + argvPtrs.push(keys.length); + keys.forEach((key) => { + argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); + }); + + const argv = offset; + argvPtrs.forEach((ptr) => { + mem.setUint32(offset, ptr, true); + mem.setUint32(offset + 4, 0, true); + offset += 8; + }); + + while (true) { + const callbackPromise = new Promise((resolve) => { + this._resolveCallbackPromise = resolve; + }); + this._inst.exports.run(argc, argv); + if (this.exited) { + break; + } + await callbackPromise; + } + } + } + + if (isNodeJS) { + if (process.argv.length < 3) { + process.stderr.write("usage: go_js_wasm_exec [wasm binary] [arguments]\n"); + process.exit(1); + } + + const go = new Go(); + go.argv = process.argv.slice(2); + go.env = process.env; + go.exit = process.exit; + WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { + process.on("exit", () => { // Node.js exits if no callback is pending + if (!go.exited) { + console.error("error: all goroutines asleep and no JavaScript callback pending - deadlock!"); + process.exit(1); + } + }); + return go.run(result.instance); + }).catch((err) => { + console.error(err); + go.exited = true; + process.exit(1); + }); + } +})(); \ No newline at end of file diff --git a/src/tool/run/packages/bytes.go b/src/tool/run/packages/bytes.go new file mode 100644 index 0000000..650c208 --- /dev/null +++ b/src/tool/run/packages/bytes.go @@ -0,0 +1,64 @@ +package packages + +import ( + "bytes" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["bytes"] = map[string]reflect.Value{ + "Compare": reflect.ValueOf(bytes.Compare), + "Contains": reflect.ValueOf(bytes.Contains), + "Count": reflect.ValueOf(bytes.Count), + "Equal": reflect.ValueOf(bytes.Equal), + "EqualFold": reflect.ValueOf(bytes.EqualFold), + "Fields": reflect.ValueOf(bytes.Fields), + "FieldsFunc": reflect.ValueOf(bytes.FieldsFunc), + "HasPrefix": reflect.ValueOf(bytes.HasPrefix), + "HasSuffix": reflect.ValueOf(bytes.HasSuffix), + "Index": reflect.ValueOf(bytes.Index), + "IndexAny": reflect.ValueOf(bytes.IndexAny), + "IndexByte": reflect.ValueOf(bytes.IndexByte), + "IndexFunc": reflect.ValueOf(bytes.IndexFunc), + "IndexRune": reflect.ValueOf(bytes.IndexRune), + "Join": reflect.ValueOf(bytes.Join), + "LastIndex": reflect.ValueOf(bytes.LastIndex), + "LastIndexAny": reflect.ValueOf(bytes.LastIndexAny), + "LastIndexByte": reflect.ValueOf(bytes.LastIndexByte), + "LastIndexFunc": reflect.ValueOf(bytes.LastIndexFunc), + "Map": reflect.ValueOf(bytes.Map), + "NewBuffer": reflect.ValueOf(bytes.NewBuffer), + "NewBufferString": reflect.ValueOf(bytes.NewBufferString), + "NewReader": reflect.ValueOf(bytes.NewReader), + "Repeat": reflect.ValueOf(bytes.Repeat), + "Replace": reflect.ValueOf(bytes.Replace), + "Runes": reflect.ValueOf(bytes.Runes), + "Split": reflect.ValueOf(bytes.Split), + "SplitAfter": reflect.ValueOf(bytes.SplitAfter), + "SplitAfterN": reflect.ValueOf(bytes.SplitAfterN), + "SplitN": reflect.ValueOf(bytes.SplitN), + "Title": reflect.ValueOf(bytes.Title), + "ToLower": reflect.ValueOf(bytes.ToLower), + "ToLowerSpecial": reflect.ValueOf(bytes.ToLowerSpecial), + "ToTitle": reflect.ValueOf(bytes.ToTitle), + "ToTitleSpecial": reflect.ValueOf(bytes.ToTitleSpecial), + "ToUpper": reflect.ValueOf(bytes.ToUpper), + "ToUpperSpecial": reflect.ValueOf(bytes.ToUpperSpecial), + "Trim": reflect.ValueOf(bytes.Trim), + "TrimFunc": reflect.ValueOf(bytes.TrimFunc), + "TrimLeft": reflect.ValueOf(bytes.TrimLeft), + "TrimLeftFunc": reflect.ValueOf(bytes.TrimLeftFunc), + "TrimPrefix": reflect.ValueOf(bytes.TrimPrefix), + "TrimRight": reflect.ValueOf(bytes.TrimRight), + "TrimRightFunc": reflect.ValueOf(bytes.TrimRightFunc), + "TrimSpace": reflect.ValueOf(bytes.TrimSpace), + "TrimSuffix": reflect.ValueOf(bytes.TrimSuffix), + } + env.PackageTypes["bytes"] = map[string]reflect.Type{ + "Buffer": reflect.TypeOf(bytes.Buffer{}), + "Reader": reflect.TypeOf(bytes.Reader{}), + } + bytesGo17() +} diff --git a/src/tool/run/packages/bytesGo17.go b/src/tool/run/packages/bytesGo17.go new file mode 100644 index 0000000..4ef560f --- /dev/null +++ b/src/tool/run/packages/bytesGo17.go @@ -0,0 +1,14 @@ +// +build go1.7 + +package packages + +import ( + "bytes" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func bytesGo17() { + env.Packages["bytes"]["ContainsRune"] = reflect.ValueOf(bytes.ContainsRune) +} diff --git a/src/tool/run/packages/bytesNotGo17.go b/src/tool/run/packages/bytesNotGo17.go new file mode 100644 index 0000000..795edb6 --- /dev/null +++ b/src/tool/run/packages/bytesNotGo17.go @@ -0,0 +1,5 @@ +// +build !go1.7 + +package packages + +func bytesGo17() {} diff --git a/src/tool/run/packages/encoding.json.go b/src/tool/run/packages/encoding.json.go new file mode 100644 index 0000000..2fe2477 --- /dev/null +++ b/src/tool/run/packages/encoding.json.go @@ -0,0 +1,15 @@ +package packages + +import ( + "encoding/json" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["encoding/json"] = map[string]reflect.Value{ + "Marshal": reflect.ValueOf(json.Marshal), + "Unmarshal": reflect.ValueOf(json.Unmarshal), + } +} diff --git a/src/tool/run/packages/errors.go b/src/tool/run/packages/errors.go new file mode 100644 index 0000000..cf56660 --- /dev/null +++ b/src/tool/run/packages/errors.go @@ -0,0 +1,14 @@ +package packages + +import ( + "errors" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["errors"] = map[string]reflect.Value{ + "New": reflect.ValueOf(errors.New), + } +} diff --git a/src/tool/run/packages/flag.go b/src/tool/run/packages/flag.go new file mode 100644 index 0000000..538f975 --- /dev/null +++ b/src/tool/run/packages/flag.go @@ -0,0 +1,48 @@ +package packages + +import ( + "flag" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["flag"] = map[string]reflect.Value{ + "Arg": reflect.ValueOf(flag.Arg), + "Args": reflect.ValueOf(flag.Args), + "Bool": reflect.ValueOf(flag.Bool), + "BoolVar": reflect.ValueOf(flag.BoolVar), + "CommandLine": reflect.ValueOf(flag.CommandLine), + "ContinueOnError": reflect.ValueOf(flag.ContinueOnError), + "Duration": reflect.ValueOf(flag.Duration), + "DurationVar": reflect.ValueOf(flag.DurationVar), + "ErrHelp": reflect.ValueOf(flag.ErrHelp), + "ExitOnError": reflect.ValueOf(flag.ExitOnError), + "Float64": reflect.ValueOf(flag.Float64), + "Float64Var": reflect.ValueOf(flag.Float64Var), + "Int": reflect.ValueOf(flag.Int), + "Int64": reflect.ValueOf(flag.Int64), + "Int64Var": reflect.ValueOf(flag.Int64Var), + "IntVar": reflect.ValueOf(flag.IntVar), + "Lookup": reflect.ValueOf(flag.Lookup), + "NArg": reflect.ValueOf(flag.NArg), + "NFlag": reflect.ValueOf(flag.NFlag), + "NewFlagSet": reflect.ValueOf(flag.NewFlagSet), + "PanicOnError": reflect.ValueOf(flag.PanicOnError), + "Parse": reflect.ValueOf(flag.Parse), + "Parsed": reflect.ValueOf(flag.Parsed), + "PrintDefaults": reflect.ValueOf(flag.PrintDefaults), + "Set": reflect.ValueOf(flag.Set), + "String": reflect.ValueOf(flag.String), + "StringVar": reflect.ValueOf(flag.StringVar), + "Uint": reflect.ValueOf(flag.Uint), + "Uint64": reflect.ValueOf(flag.Uint64), + "Uint64Var": reflect.ValueOf(flag.Uint64Var), + "UintVar": reflect.ValueOf(flag.UintVar), + "Usage": reflect.ValueOf(flag.Usage), + "Var": reflect.ValueOf(flag.Var), + "Visit": reflect.ValueOf(flag.Visit), + "VisitAll": reflect.ValueOf(flag.VisitAll), + } +} diff --git a/src/tool/run/packages/fmt.go b/src/tool/run/packages/fmt.go new file mode 100644 index 0000000..d267740 --- /dev/null +++ b/src/tool/run/packages/fmt.go @@ -0,0 +1,32 @@ +package packages + +import ( + "fmt" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["fmt"] = map[string]reflect.Value{ + "Errorf": reflect.ValueOf(fmt.Errorf), + "Fprint": reflect.ValueOf(fmt.Fprint), + "Fprintf": reflect.ValueOf(fmt.Fprintf), + "Fprintln": reflect.ValueOf(fmt.Fprintln), + "Fscan": reflect.ValueOf(fmt.Fscan), + "Fscanf": reflect.ValueOf(fmt.Fscanf), + "Fscanln": reflect.ValueOf(fmt.Fscanln), + "Print": reflect.ValueOf(fmt.Print), + "Printf": reflect.ValueOf(fmt.Printf), + "Println": reflect.ValueOf(fmt.Println), + "Scan": reflect.ValueOf(fmt.Scan), + "Scanf": reflect.ValueOf(fmt.Scanf), + "Scanln": reflect.ValueOf(fmt.Scanln), + "Sprint": reflect.ValueOf(fmt.Sprint), + "Sprintf": reflect.ValueOf(fmt.Sprintf), + "Sprintln": reflect.ValueOf(fmt.Sprintln), + "Sscan": reflect.ValueOf(fmt.Sscan), + "Sscanf": reflect.ValueOf(fmt.Sscanf), + "Sscanln": reflect.ValueOf(fmt.Sscanln), + } +} diff --git a/src/tool/run/packages/io.go b/src/tool/run/packages/io.go new file mode 100644 index 0000000..eaa103b --- /dev/null +++ b/src/tool/run/packages/io.go @@ -0,0 +1,30 @@ +package packages + +import ( + "io" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["io"] = map[string]reflect.Value{ + "Copy": reflect.ValueOf(io.Copy), + "CopyN": reflect.ValueOf(io.CopyN), + "EOF": reflect.ValueOf(io.EOF), + "ErrClosedPipe": reflect.ValueOf(io.ErrClosedPipe), + "ErrNoProgress": reflect.ValueOf(io.ErrNoProgress), + "ErrShortBuffer": reflect.ValueOf(io.ErrShortBuffer), + "ErrShortWrite": reflect.ValueOf(io.ErrShortWrite), + "ErrUnexpectedEOF": reflect.ValueOf(io.ErrUnexpectedEOF), + "LimitReader": reflect.ValueOf(io.LimitReader), + "MultiReader": reflect.ValueOf(io.MultiReader), + "MultiWriter": reflect.ValueOf(io.MultiWriter), + "NewSectionReader": reflect.ValueOf(io.NewSectionReader), + "Pipe": reflect.ValueOf(io.Pipe), + "ReadAtLeast": reflect.ValueOf(io.ReadAtLeast), + "ReadFull": reflect.ValueOf(io.ReadFull), + "TeeReader": reflect.ValueOf(io.TeeReader), + "WriteString": reflect.ValueOf(io.WriteString), + } +} diff --git a/src/tool/run/packages/io.ioutil.go b/src/tool/run/packages/io.ioutil.go new file mode 100644 index 0000000..50cb0ba --- /dev/null +++ b/src/tool/run/packages/io.ioutil.go @@ -0,0 +1,17 @@ +package packages + +import ( + "io/ioutil" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["io/ioutil"] = map[string]reflect.Value{ + "ReadAll": reflect.ValueOf(ioutil.ReadAll), + "ReadDir": reflect.ValueOf(ioutil.ReadDir), + "ReadFile": reflect.ValueOf(ioutil.ReadFile), + "WriteFile": reflect.ValueOf(ioutil.WriteFile), + } +} diff --git a/src/tool/run/packages/log.go b/src/tool/run/packages/log.go new file mode 100644 index 0000000..e441792 --- /dev/null +++ b/src/tool/run/packages/log.go @@ -0,0 +1,29 @@ +package packages + +import ( + "log" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["log"] = map[string]reflect.Value{ + "Fatal": reflect.ValueOf(log.Fatal), + "Fatalf": reflect.ValueOf(log.Fatalf), + "Fatalln": reflect.ValueOf(log.Fatalln), + "Flags": reflect.ValueOf(log.Flags), + "New": reflect.ValueOf(log.New), + "Output": reflect.ValueOf(log.Output), + "Panic": reflect.ValueOf(log.Panic), + "Panicf": reflect.ValueOf(log.Panicf), + "Panicln": reflect.ValueOf(log.Panicln), + "Prefix": reflect.ValueOf(log.Prefix), + "Print": reflect.ValueOf(log.Print), + "Printf": reflect.ValueOf(log.Printf), + "Println": reflect.ValueOf(log.Println), + "SetFlags": reflect.ValueOf(log.SetFlags), + "SetOutput": reflect.ValueOf(log.SetOutput), + "SetPrefix": reflect.ValueOf(log.SetPrefix), + } +} diff --git a/src/tool/run/packages/math.big.go b/src/tool/run/packages/math.big.go new file mode 100644 index 0000000..8176e74 --- /dev/null +++ b/src/tool/run/packages/math.big.go @@ -0,0 +1,32 @@ +package packages + +import ( + "math/big" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["math/big"] = map[string]reflect.Value{ + "Above": reflect.ValueOf(big.Above), + "AwayFromZero": reflect.ValueOf(big.AwayFromZero), + "Below": reflect.ValueOf(big.Below), + "Exact": reflect.ValueOf(big.Exact), + "Jacobi": reflect.ValueOf(big.Jacobi), + "MaxBase": reflect.ValueOf(big.MaxBase), + "MaxExp": reflect.ValueOf(big.MaxExp), + // TODO: https://github.com/surdeus/goblin/src/tool/run/issues/49 + // "MaxPrec": reflect.ValueOf(big.MaxPrec), + "MinExp": reflect.ValueOf(big.MinExp), + "NewFloat": reflect.ValueOf(big.NewFloat), + "NewInt": reflect.ValueOf(big.NewInt), + "NewRat": reflect.ValueOf(big.NewRat), + "ParseFloat": reflect.ValueOf(big.ParseFloat), + "ToNearestAway": reflect.ValueOf(big.ToNearestAway), + "ToNearestEven": reflect.ValueOf(big.ToNearestEven), + "ToNegativeInf": reflect.ValueOf(big.ToNegativeInf), + "ToPositiveInf": reflect.ValueOf(big.ToPositiveInf), + "ToZero": reflect.ValueOf(big.ToZero), + } +} diff --git a/src/tool/run/packages/math.go b/src/tool/run/packages/math.go new file mode 100644 index 0000000..c20536c --- /dev/null +++ b/src/tool/run/packages/math.go @@ -0,0 +1,74 @@ +package packages + +import ( + "math" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["math"] = map[string]reflect.Value{ + "Abs": reflect.ValueOf(math.Abs), + "Acos": reflect.ValueOf(math.Acos), + "Acosh": reflect.ValueOf(math.Acosh), + "Asin": reflect.ValueOf(math.Asin), + "Asinh": reflect.ValueOf(math.Asinh), + "Atan": reflect.ValueOf(math.Atan), + "Atan2": reflect.ValueOf(math.Atan2), + "Atanh": reflect.ValueOf(math.Atanh), + "Cbrt": reflect.ValueOf(math.Cbrt), + "Ceil": reflect.ValueOf(math.Ceil), + "Copysign": reflect.ValueOf(math.Copysign), + "Cos": reflect.ValueOf(math.Cos), + "Cosh": reflect.ValueOf(math.Cosh), + "Dim": reflect.ValueOf(math.Dim), + "Erf": reflect.ValueOf(math.Erf), + "Erfc": reflect.ValueOf(math.Erfc), + "Exp": reflect.ValueOf(math.Exp), + "Exp2": reflect.ValueOf(math.Exp2), + "Expm1": reflect.ValueOf(math.Expm1), + "Float32bits": reflect.ValueOf(math.Float32bits), + "Float32frombits": reflect.ValueOf(math.Float32frombits), + "Float64bits": reflect.ValueOf(math.Float64bits), + "Float64frombits": reflect.ValueOf(math.Float64frombits), + "Floor": reflect.ValueOf(math.Floor), + "Frexp": reflect.ValueOf(math.Frexp), + "Gamma": reflect.ValueOf(math.Gamma), + "Hypot": reflect.ValueOf(math.Hypot), + "Ilogb": reflect.ValueOf(math.Ilogb), + "Inf": reflect.ValueOf(math.Inf), + "IsInf": reflect.ValueOf(math.IsInf), + "IsNaN": reflect.ValueOf(math.IsNaN), + "J0": reflect.ValueOf(math.J0), + "J1": reflect.ValueOf(math.J1), + "Jn": reflect.ValueOf(math.Jn), + "Ldexp": reflect.ValueOf(math.Ldexp), + "Lgamma": reflect.ValueOf(math.Lgamma), + "Log": reflect.ValueOf(math.Log), + "Log10": reflect.ValueOf(math.Log10), + "Log1p": reflect.ValueOf(math.Log1p), + "Log2": reflect.ValueOf(math.Log2), + "Logb": reflect.ValueOf(math.Logb), + "Max": reflect.ValueOf(math.Max), + "Min": reflect.ValueOf(math.Min), + "Mod": reflect.ValueOf(math.Mod), + "Modf": reflect.ValueOf(math.Modf), + "NaN": reflect.ValueOf(math.NaN), + "Nextafter": reflect.ValueOf(math.Nextafter), + "Pow": reflect.ValueOf(math.Pow), + "Pow10": reflect.ValueOf(math.Pow10), + "Remainder": reflect.ValueOf(math.Remainder), + "Signbit": reflect.ValueOf(math.Signbit), + "Sin": reflect.ValueOf(math.Sin), + "Sincos": reflect.ValueOf(math.Sincos), + "Sinh": reflect.ValueOf(math.Sinh), + "Sqrt": reflect.ValueOf(math.Sqrt), + "Tan": reflect.ValueOf(math.Tan), + "Tanh": reflect.ValueOf(math.Tanh), + "Trunc": reflect.ValueOf(math.Trunc), + "Y0": reflect.ValueOf(math.Y0), + "Y1": reflect.ValueOf(math.Y1), + "Yn": reflect.ValueOf(math.Yn), + } +} diff --git a/src/tool/run/packages/math.rand.go b/src/tool/run/packages/math.rand.go new file mode 100644 index 0000000..cf68ca6 --- /dev/null +++ b/src/tool/run/packages/math.rand.go @@ -0,0 +1,26 @@ +package packages + +import ( + "math/rand" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["math/rand"] = map[string]reflect.Value{ + "ExpFloat64": reflect.ValueOf(rand.ExpFloat64), + "Float32": reflect.ValueOf(rand.Float32), + "Float64": reflect.ValueOf(rand.Float64), + "Int": reflect.ValueOf(rand.Int), + "Int31": reflect.ValueOf(rand.Int31), + "Int31n": reflect.ValueOf(rand.Int31n), + "Int63": reflect.ValueOf(rand.Int63), + "Int63n": reflect.ValueOf(rand.Int63n), + "Intn": reflect.ValueOf(rand.Intn), + "NormFloat64": reflect.ValueOf(rand.NormFloat64), + "Perm": reflect.ValueOf(rand.Perm), + "Seed": reflect.ValueOf(rand.Seed), + "Uint32": reflect.ValueOf(rand.Uint32), + } +} diff --git a/src/tool/run/packages/net.go b/src/tool/run/packages/net.go new file mode 100644 index 0000000..a068ba1 --- /dev/null +++ b/src/tool/run/packages/net.go @@ -0,0 +1,76 @@ +// +build !appengine + +package packages + +import ( + "net" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["net"] = map[string]reflect.Value{ + "CIDRMask": reflect.ValueOf(net.CIDRMask), + "Dial": reflect.ValueOf(net.Dial), + "DialIP": reflect.ValueOf(net.DialIP), + "DialTCP": reflect.ValueOf(net.DialTCP), + "DialTimeout": reflect.ValueOf(net.DialTimeout), + "DialUDP": reflect.ValueOf(net.DialUDP), + "DialUnix": reflect.ValueOf(net.DialUnix), + "ErrWriteToConnected": reflect.ValueOf(net.ErrWriteToConnected), + "FileConn": reflect.ValueOf(net.FileConn), + "FileListener": reflect.ValueOf(net.FileListener), + "FilePacketConn": reflect.ValueOf(net.FilePacketConn), + "FlagBroadcast": reflect.ValueOf(net.FlagBroadcast), + "FlagLoopback": reflect.ValueOf(net.FlagLoopback), + "FlagMulticast": reflect.ValueOf(net.FlagMulticast), + "FlagPointToPoint": reflect.ValueOf(net.FlagPointToPoint), + "FlagUp": reflect.ValueOf(net.FlagUp), + "IPv4": reflect.ValueOf(net.IPv4), + "IPv4Mask": reflect.ValueOf(net.IPv4Mask), + "IPv4allrouter": reflect.ValueOf(net.IPv4allrouter), + "IPv4allsys": reflect.ValueOf(net.IPv4allsys), + "IPv4bcast": reflect.ValueOf(net.IPv4bcast), + "IPv4len": reflect.ValueOf(net.IPv4len), + "IPv4zero": reflect.ValueOf(net.IPv4zero), + "IPv6interfacelocalallnodes": reflect.ValueOf(net.IPv6interfacelocalallnodes), + "IPv6len": reflect.ValueOf(net.IPv6len), + "IPv6linklocalallnodes": reflect.ValueOf(net.IPv6linklocalallnodes), + "IPv6linklocalallrouters": reflect.ValueOf(net.IPv6linklocalallrouters), + "IPv6loopback": reflect.ValueOf(net.IPv6loopback), + "IPv6unspecified": reflect.ValueOf(net.IPv6unspecified), + "IPv6zero": reflect.ValueOf(net.IPv6zero), + "InterfaceAddrs": reflect.ValueOf(net.InterfaceAddrs), + "InterfaceByIndex": reflect.ValueOf(net.InterfaceByIndex), + "InterfaceByName": reflect.ValueOf(net.InterfaceByName), + "Interfaces": reflect.ValueOf(net.Interfaces), + "JoinHostPort": reflect.ValueOf(net.JoinHostPort), + "Listen": reflect.ValueOf(net.Listen), + "ListenIP": reflect.ValueOf(net.ListenIP), + "ListenMulticastUDP": reflect.ValueOf(net.ListenMulticastUDP), + "ListenPacket": reflect.ValueOf(net.ListenPacket), + "ListenTCP": reflect.ValueOf(net.ListenTCP), + "ListenUDP": reflect.ValueOf(net.ListenUDP), + "ListenUnix": reflect.ValueOf(net.ListenUnix), + "ListenUnixgram": reflect.ValueOf(net.ListenUnixgram), + "LookupAddr": reflect.ValueOf(net.LookupAddr), + "LookupCNAME": reflect.ValueOf(net.LookupCNAME), + "LookupHost": reflect.ValueOf(net.LookupHost), + "LookupIP": reflect.ValueOf(net.LookupIP), + "LookupMX": reflect.ValueOf(net.LookupMX), + "LookupNS": reflect.ValueOf(net.LookupNS), + "LookupPort": reflect.ValueOf(net.LookupPort), + "LookupSRV": reflect.ValueOf(net.LookupSRV), + "LookupTXT": reflect.ValueOf(net.LookupTXT), + "ParseCIDR": reflect.ValueOf(net.ParseCIDR), + "ParseIP": reflect.ValueOf(net.ParseIP), + "ParseMAC": reflect.ValueOf(net.ParseMAC), + "Pipe": reflect.ValueOf(net.Pipe), + "ResolveIPAddr": reflect.ValueOf(net.ResolveIPAddr), + "ResolveTCPAddr": reflect.ValueOf(net.ResolveTCPAddr), + "ResolveUDPAddr": reflect.ValueOf(net.ResolveUDPAddr), + "ResolveUnixAddr": reflect.ValueOf(net.ResolveUnixAddr), + "SplitHostPort": reflect.ValueOf(net.SplitHostPort), + } +} diff --git a/src/tool/run/packages/net.http.cookiejar.go b/src/tool/run/packages/net.http.cookiejar.go new file mode 100644 index 0000000..1fd0133 --- /dev/null +++ b/src/tool/run/packages/net.http.cookiejar.go @@ -0,0 +1,17 @@ +package packages + +import ( + "net/http/cookiejar" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["net/http/cookiejar"] = map[string]reflect.Value{ + "New": reflect.ValueOf(cookiejar.New), + } + env.PackageTypes["net/http/cookiejar"] = map[string]reflect.Type{ + "Options": reflect.TypeOf(cookiejar.Options{}), + } +} diff --git a/src/tool/run/packages/net.http.go b/src/tool/run/packages/net.http.go new file mode 100644 index 0000000..1fd5c1c --- /dev/null +++ b/src/tool/run/packages/net.http.go @@ -0,0 +1,32 @@ +// +build !appengine + +package packages + +import ( + "net/http" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["net/http"] = map[string]reflect.Value{ + "DefaultClient": reflect.ValueOf(http.DefaultClient), + "DefaultServeMux": reflect.ValueOf(http.DefaultServeMux), + "DefaultTransport": reflect.ValueOf(http.DefaultTransport), + "Handle": reflect.ValueOf(http.Handle), + "HandleFunc": reflect.ValueOf(http.HandleFunc), + "ListenAndServe": reflect.ValueOf(http.ListenAndServe), + "ListenAndServeTLS": reflect.ValueOf(http.ListenAndServeTLS), + "NewRequest": reflect.ValueOf(http.NewRequest), + "NewServeMux": reflect.ValueOf(http.NewServeMux), + "Serve": reflect.ValueOf(http.Serve), + "SetCookie": reflect.ValueOf(http.SetCookie), + } + env.PackageTypes["net/http"] = map[string]reflect.Type{ + "Client": reflect.TypeOf(http.Client{}), + "Cookie": reflect.TypeOf(http.Cookie{}), + "Request": reflect.TypeOf(http.Request{}), + "Response": reflect.TypeOf(http.Response{}), + } +} diff --git a/src/tool/run/packages/net.url.go b/src/tool/run/packages/net.url.go new file mode 100644 index 0000000..2c3b6d9 --- /dev/null +++ b/src/tool/run/packages/net.url.go @@ -0,0 +1,27 @@ +// +build !appengine + +package packages + +import ( + "net/url" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["net/url"] = map[string]reflect.Value{ + "QueryEscape": reflect.ValueOf(url.QueryEscape), + "QueryUnescape": reflect.ValueOf(url.QueryUnescape), + "Parse": reflect.ValueOf(url.Parse), + "ParseRequestURI": reflect.ValueOf(url.ParseRequestURI), + "User": reflect.ValueOf(url.User), + "UserPassword": reflect.ValueOf(url.UserPassword), + "ParseQuery": reflect.ValueOf(url.ParseQuery), + } + env.PackageTypes["net/url"] = map[string]reflect.Type{ + "Error": reflect.TypeOf(url.Error{}), + "URL": reflect.TypeOf(url.URL{}), + "Values": reflect.TypeOf(url.Values{}), + } +} diff --git a/src/tool/run/packages/os.exec.go b/src/tool/run/packages/os.exec.go new file mode 100644 index 0000000..9fe49ea --- /dev/null +++ b/src/tool/run/packages/os.exec.go @@ -0,0 +1,16 @@ +package packages + +import ( + "os/exec" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["os/exec"] = map[string]reflect.Value{ + "ErrNotFound": reflect.ValueOf(exec.ErrNotFound), + "LookPath": reflect.ValueOf(exec.LookPath), + "Command": reflect.ValueOf(exec.Command), + } +} diff --git a/src/tool/run/packages/os.go b/src/tool/run/packages/os.go new file mode 100644 index 0000000..ea01757 --- /dev/null +++ b/src/tool/run/packages/os.go @@ -0,0 +1,102 @@ +package packages + +import ( + "os" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["os"] = map[string]reflect.Value{ + "Args": reflect.ValueOf(os.Args), + "Chdir": reflect.ValueOf(os.Chdir), + "Chmod": reflect.ValueOf(os.Chmod), + "Chown": reflect.ValueOf(os.Chown), + "Chtimes": reflect.ValueOf(os.Chtimes), + "Clearenv": reflect.ValueOf(os.Clearenv), + "Create": reflect.ValueOf(os.Create), + "DevNull": reflect.ValueOf(os.DevNull), + "Environ": reflect.ValueOf(os.Environ), + "ErrExist": reflect.ValueOf(os.ErrExist), + "ErrInvalid": reflect.ValueOf(os.ErrInvalid), + "ErrNotExist": reflect.ValueOf(os.ErrNotExist), + "ErrPermission": reflect.ValueOf(os.ErrPermission), + "Exit": reflect.ValueOf(os.Exit), + "Expand": reflect.ValueOf(os.Expand), + "ExpandEnv": reflect.ValueOf(os.ExpandEnv), + "FindProcess": reflect.ValueOf(os.FindProcess), + "Getegid": reflect.ValueOf(os.Getegid), + "Getenv": reflect.ValueOf(os.Getenv), + "Geteuid": reflect.ValueOf(os.Geteuid), + "Getgid": reflect.ValueOf(os.Getgid), + "Getgroups": reflect.ValueOf(os.Getgroups), + "Getpagesize": reflect.ValueOf(os.Getpagesize), + "Getpid": reflect.ValueOf(os.Getpid), + "Getuid": reflect.ValueOf(os.Getuid), + "Getwd": reflect.ValueOf(os.Getwd), + "Hostname": reflect.ValueOf(os.Hostname), + "Interrupt": reflect.ValueOf(os.Interrupt), + "IsExist": reflect.ValueOf(os.IsExist), + "IsNotExist": reflect.ValueOf(os.IsNotExist), + "IsPathSeparator": reflect.ValueOf(os.IsPathSeparator), + "IsPermission": reflect.ValueOf(os.IsPermission), + "Kill": reflect.ValueOf(os.Kill), + "Lchown": reflect.ValueOf(os.Lchown), + "Link": reflect.ValueOf(os.Link), + "Lstat": reflect.ValueOf(os.Lstat), + "Mkdir": reflect.ValueOf(os.Mkdir), + "MkdirAll": reflect.ValueOf(os.MkdirAll), + "ModeAppend": reflect.ValueOf(os.ModeAppend), + "ModeCharDevice": reflect.ValueOf(os.ModeCharDevice), + "ModeDevice": reflect.ValueOf(os.ModeDevice), + "ModeDir": reflect.ValueOf(os.ModeDir), + "ModeExclusive": reflect.ValueOf(os.ModeExclusive), + "ModeNamedPipe": reflect.ValueOf(os.ModeNamedPipe), + "ModePerm": reflect.ValueOf(os.ModePerm), + "ModeSetgid": reflect.ValueOf(os.ModeSetgid), + "ModeSetuid": reflect.ValueOf(os.ModeSetuid), + "ModeSocket": reflect.ValueOf(os.ModeSocket), + "ModeSticky": reflect.ValueOf(os.ModeSticky), + "ModeSymlink": reflect.ValueOf(os.ModeSymlink), + "ModeTemporary": reflect.ValueOf(os.ModeTemporary), + "ModeType": reflect.ValueOf(os.ModeType), + "NewFile": reflect.ValueOf(os.NewFile), + "NewSyscallError": reflect.ValueOf(os.NewSyscallError), + "O_APPEND": reflect.ValueOf(os.O_APPEND), + "O_CREATE": reflect.ValueOf(os.O_CREATE), + "O_EXCL": reflect.ValueOf(os.O_EXCL), + "O_RDONLY": reflect.ValueOf(os.O_RDONLY), + "O_RDWR": reflect.ValueOf(os.O_RDWR), + "O_SYNC": reflect.ValueOf(os.O_SYNC), + "O_TRUNC": reflect.ValueOf(os.O_TRUNC), + "O_WRONLY": reflect.ValueOf(os.O_WRONLY), + "Open": reflect.ValueOf(os.Open), + "OpenFile": reflect.ValueOf(os.OpenFile), + "PathListSeparator": reflect.ValueOf(os.PathListSeparator), + "PathSeparator": reflect.ValueOf(os.PathSeparator), + "Pipe": reflect.ValueOf(os.Pipe), + "Readlink": reflect.ValueOf(os.Readlink), + "Remove": reflect.ValueOf(os.Remove), + "RemoveAll": reflect.ValueOf(os.RemoveAll), + "Rename": reflect.ValueOf(os.Rename), + "SEEK_CUR": reflect.ValueOf(os.SEEK_CUR), + "SEEK_END": reflect.ValueOf(os.SEEK_END), + "SEEK_SET": reflect.ValueOf(os.SEEK_SET), + "SameFile": reflect.ValueOf(os.SameFile), + "Setenv": reflect.ValueOf(os.Setenv), + "StartProcess": reflect.ValueOf(os.StartProcess), + "Stat": reflect.ValueOf(os.Stat), + "Stderr": reflect.ValueOf(os.Stderr), + "Stdin": reflect.ValueOf(os.Stdin), + "Stdout": reflect.ValueOf(os.Stdout), + "Symlink": reflect.ValueOf(os.Symlink), + "TempDir": reflect.ValueOf(os.TempDir), + "Truncate": reflect.ValueOf(os.Truncate), + } + var signal os.Signal + env.PackageTypes["os"] = map[string]reflect.Type{ + "Signal": reflect.TypeOf(&signal).Elem(), + } + osNotAppEngine() +} diff --git a/src/tool/run/packages/os.signal.go b/src/tool/run/packages/os.signal.go new file mode 100644 index 0000000..4603c77 --- /dev/null +++ b/src/tool/run/packages/os.signal.go @@ -0,0 +1,15 @@ +package packages + +import ( + "os/signal" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["os/signal"] = map[string]reflect.Value{ + "Notify": reflect.ValueOf(signal.Notify), + "Stop": reflect.ValueOf(signal.Stop), + } +} diff --git a/src/tool/run/packages/osAppEngine.go b/src/tool/run/packages/osAppEngine.go new file mode 100644 index 0000000..64892f8 --- /dev/null +++ b/src/tool/run/packages/osAppEngine.go @@ -0,0 +1,5 @@ +// +build appengine + +package packages + +func osNotAppEngine() {} diff --git a/src/tool/run/packages/osNotAppEngine.go b/src/tool/run/packages/osNotAppEngine.go new file mode 100644 index 0000000..5997ea2 --- /dev/null +++ b/src/tool/run/packages/osNotAppEngine.go @@ -0,0 +1,14 @@ +// +build !appengine + +package packages + +import ( + "os" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func osNotAppEngine() { + env.Packages["os"]["Getppid"] = reflect.ValueOf(os.Getppid) +} diff --git a/src/tool/run/packages/path.filepath.go b/src/tool/run/packages/path.filepath.go new file mode 100644 index 0000000..585c3a0 --- /dev/null +++ b/src/tool/run/packages/path.filepath.go @@ -0,0 +1,30 @@ +package packages + +import ( + "path/filepath" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["path/filepath"] = map[string]reflect.Value{ + "Abs": reflect.ValueOf(filepath.Abs), + "Base": reflect.ValueOf(filepath.Base), + "Clean": reflect.ValueOf(filepath.Clean), + "Dir": reflect.ValueOf(filepath.Dir), + "EvalSymlinks": reflect.ValueOf(filepath.EvalSymlinks), + "Ext": reflect.ValueOf(filepath.Ext), + "FromSlash": reflect.ValueOf(filepath.FromSlash), + "Glob": reflect.ValueOf(filepath.Glob), + "HasPrefix": reflect.ValueOf(filepath.HasPrefix), + "IsAbs": reflect.ValueOf(filepath.IsAbs), + "Join": reflect.ValueOf(filepath.Join), + "Match": reflect.ValueOf(filepath.Match), + "Rel": reflect.ValueOf(filepath.Rel), + "Split": reflect.ValueOf(filepath.Split), + "SplitList": reflect.ValueOf(filepath.SplitList), + "ToSlash": reflect.ValueOf(filepath.ToSlash), + "VolumeName": reflect.ValueOf(filepath.VolumeName), + } +} diff --git a/src/tool/run/packages/path.go b/src/tool/run/packages/path.go new file mode 100644 index 0000000..e615818 --- /dev/null +++ b/src/tool/run/packages/path.go @@ -0,0 +1,22 @@ +package packages + +import ( + "path" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["path"] = map[string]reflect.Value{ + "Base": reflect.ValueOf(path.Base), + "Clean": reflect.ValueOf(path.Clean), + "Dir": reflect.ValueOf(path.Dir), + "ErrBadPattern": reflect.ValueOf(path.ErrBadPattern), + "Ext": reflect.ValueOf(path.Ext), + "IsAbs": reflect.ValueOf(path.IsAbs), + "Join": reflect.ValueOf(path.Join), + "Match": reflect.ValueOf(path.Match), + "Split": reflect.ValueOf(path.Split), + } +} diff --git a/src/tool/run/packages/regexp.go b/src/tool/run/packages/regexp.go new file mode 100644 index 0000000..96294bb --- /dev/null +++ b/src/tool/run/packages/regexp.go @@ -0,0 +1,21 @@ +package packages + +import ( + "reflect" + "regexp" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["regexp"] = map[string]reflect.Value{ + "Match": reflect.ValueOf(regexp.Match), + "MatchReader": reflect.ValueOf(regexp.MatchReader), + "MatchString": reflect.ValueOf(regexp.MatchString), + "QuoteMeta": reflect.ValueOf(regexp.QuoteMeta), + "Compile": reflect.ValueOf(regexp.Compile), + "CompilePOSIX": reflect.ValueOf(regexp.CompilePOSIX), + "MustCompile": reflect.ValueOf(regexp.MustCompile), + "MustCompilePOSIX": reflect.ValueOf(regexp.MustCompilePOSIX), + } +} diff --git a/src/tool/run/packages/runtime.go b/src/tool/run/packages/runtime.go new file mode 100644 index 0000000..22e9534 --- /dev/null +++ b/src/tool/run/packages/runtime.go @@ -0,0 +1,19 @@ +package packages + +import ( + "reflect" + "runtime" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["runtime"] = map[string]reflect.Value{ + "GC": reflect.ValueOf(runtime.GC), + "GOARCH": reflect.ValueOf(runtime.GOARCH), + "GOMAXPROCS": reflect.ValueOf(runtime.GOMAXPROCS), + "GOOS": reflect.ValueOf(runtime.GOOS), + "GOROOT": reflect.ValueOf(runtime.GOROOT), + "Version": reflect.ValueOf(runtime.Version), + } +} diff --git a/src/tool/run/packages/sort.go b/src/tool/run/packages/sort.go new file mode 100644 index 0000000..c98d147 --- /dev/null +++ b/src/tool/run/packages/sort.go @@ -0,0 +1,44 @@ +package packages + +import ( + "reflect" + "sort" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +// SortFuncsStruct provides functions to be used with Sort +type SortFuncsStruct struct { + LenFunc func() int + LessFunc func(i, j int) bool + SwapFunc func(i, j int) +} + +func (s SortFuncsStruct) Len() int { return s.LenFunc() } +func (s SortFuncsStruct) Less(i, j int) bool { return s.LessFunc(i, j) } +func (s SortFuncsStruct) Swap(i, j int) { s.SwapFunc(i, j) } + +func init() { + env.Packages["sort"] = map[string]reflect.Value{ + "Float64s": reflect.ValueOf(sort.Float64s), + "Float64sAreSorted": reflect.ValueOf(sort.Float64sAreSorted), + "Ints": reflect.ValueOf(sort.Ints), + "IntsAreSorted": reflect.ValueOf(sort.IntsAreSorted), + "IsSorted": reflect.ValueOf(sort.IsSorted), + "Search": reflect.ValueOf(sort.Search), + "SearchFloat64s": reflect.ValueOf(sort.SearchFloat64s), + "SearchInts": reflect.ValueOf(sort.SearchInts), + "SearchStrings": reflect.ValueOf(sort.SearchStrings), + "Sort": reflect.ValueOf(sort.Sort), + "Stable": reflect.ValueOf(sort.Stable), + "Strings": reflect.ValueOf(sort.Strings), + "StringsAreSorted": reflect.ValueOf(sort.StringsAreSorted), + } + env.PackageTypes["sort"] = map[string]reflect.Type{ + "Float64Slice": reflect.TypeOf(sort.Float64Slice{}), + "IntSlice": reflect.TypeOf(sort.IntSlice{}), + "StringSlice": reflect.TypeOf(sort.StringSlice{}), + "SortFuncsStruct": reflect.TypeOf(&SortFuncsStruct{}), + } + sortGo18() +} diff --git a/src/tool/run/packages/sortGo18.go b/src/tool/run/packages/sortGo18.go new file mode 100644 index 0000000..d167c5f --- /dev/null +++ b/src/tool/run/packages/sortGo18.go @@ -0,0 +1,16 @@ +// +build go1.8 + +package packages + +import ( + "reflect" + "sort" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func sortGo18() { + env.Packages["sort"]["Slice"] = reflect.ValueOf(sort.Slice) + env.Packages["sort"]["SliceIsSorted"] = reflect.ValueOf(sort.SliceIsSorted) + env.Packages["sort"]["SliceStable"] = reflect.ValueOf(sort.SliceStable) +} diff --git a/src/tool/run/packages/sortNotGo18.go b/src/tool/run/packages/sortNotGo18.go new file mode 100644 index 0000000..0588911 --- /dev/null +++ b/src/tool/run/packages/sortNotGo18.go @@ -0,0 +1,5 @@ +// +build !go1.8 + +package packages + +func sortGo18() {} diff --git a/src/tool/run/packages/strconv.go b/src/tool/run/packages/strconv.go new file mode 100644 index 0000000..191abc4 --- /dev/null +++ b/src/tool/run/packages/strconv.go @@ -0,0 +1,23 @@ +package packages + +import ( + "reflect" + "strconv" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["strconv"] = map[string]reflect.Value{ + "FormatBool": reflect.ValueOf(strconv.FormatBool), + "FormatFloat": reflect.ValueOf(strconv.FormatFloat), + "FormatInt": reflect.ValueOf(strconv.FormatInt), + "FormatUint": reflect.ValueOf(strconv.FormatUint), + "ParseBool": reflect.ValueOf(strconv.ParseBool), + "ParseFloat": reflect.ValueOf(strconv.ParseFloat), + "ParseInt": reflect.ValueOf(strconv.ParseInt), + "ParseUint": reflect.ValueOf(strconv.ParseUint), + "Atoi": reflect.ValueOf(strconv.Atoi), + "Itoa": reflect.ValueOf(strconv.Itoa), + } +} diff --git a/src/tool/run/packages/strings.go b/src/tool/run/packages/strings.go new file mode 100644 index 0000000..9a1fae0 --- /dev/null +++ b/src/tool/run/packages/strings.go @@ -0,0 +1,57 @@ +package packages + +import ( + "reflect" + "strings" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["strings"] = map[string]reflect.Value{ + "Contains": reflect.ValueOf(strings.Contains), + "ContainsAny": reflect.ValueOf(strings.ContainsAny), + "ContainsRune": reflect.ValueOf(strings.ContainsRune), + "Count": reflect.ValueOf(strings.Count), + "EqualFold": reflect.ValueOf(strings.EqualFold), + "Fields": reflect.ValueOf(strings.Fields), + "FieldsFunc": reflect.ValueOf(strings.FieldsFunc), + "HasPrefix": reflect.ValueOf(strings.HasPrefix), + "HasSuffix": reflect.ValueOf(strings.HasSuffix), + "Index": reflect.ValueOf(strings.Index), + "IndexAny": reflect.ValueOf(strings.IndexAny), + "IndexByte": reflect.ValueOf(strings.IndexByte), + "IndexFunc": reflect.ValueOf(strings.IndexFunc), + "IndexRune": reflect.ValueOf(strings.IndexRune), + "Join": reflect.ValueOf(strings.Join), + "LastIndex": reflect.ValueOf(strings.LastIndex), + "LastIndexAny": reflect.ValueOf(strings.LastIndexAny), + "LastIndexFunc": reflect.ValueOf(strings.LastIndexFunc), + "Map": reflect.ValueOf(strings.Map), + "NewReader": reflect.ValueOf(strings.NewReader), + "NewReplacer": reflect.ValueOf(strings.NewReplacer), + "Repeat": reflect.ValueOf(strings.Repeat), + "Replace": reflect.ValueOf(strings.Replace), + "Split": reflect.ValueOf(strings.Split), + "SplitAfter": reflect.ValueOf(strings.SplitAfter), + "SplitAfterN": reflect.ValueOf(strings.SplitAfterN), + "SplitN": reflect.ValueOf(strings.SplitN), + "Title": reflect.ValueOf(strings.Title), + "ToLower": reflect.ValueOf(strings.ToLower), + "ToLowerSpecial": reflect.ValueOf(strings.ToLowerSpecial), + "ToTitle": reflect.ValueOf(strings.ToTitle), + "ToTitleSpecial": reflect.ValueOf(strings.ToTitleSpecial), + "ToUpper": reflect.ValueOf(strings.ToUpper), + "ToUpperSpecial": reflect.ValueOf(strings.ToUpperSpecial), + "Trim": reflect.ValueOf(strings.Trim), + "TrimFunc": reflect.ValueOf(strings.TrimFunc), + "TrimLeft": reflect.ValueOf(strings.TrimLeft), + "TrimLeftFunc": reflect.ValueOf(strings.TrimLeftFunc), + "TrimPrefix": reflect.ValueOf(strings.TrimPrefix), + "TrimRight": reflect.ValueOf(strings.TrimRight), + "TrimRightFunc": reflect.ValueOf(strings.TrimRightFunc), + "TrimSpace": reflect.ValueOf(strings.TrimSpace), + "TrimSuffix": reflect.ValueOf(strings.TrimSuffix), + } + stringsGo110() +} diff --git a/src/tool/run/packages/stringsGo110.go b/src/tool/run/packages/stringsGo110.go new file mode 100644 index 0000000..abc3eb9 --- /dev/null +++ b/src/tool/run/packages/stringsGo110.go @@ -0,0 +1,16 @@ +// +build go1.10 + +package packages + +import ( + "reflect" + "strings" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func stringsGo110() { + env.PackageTypes["strings"] = map[string]reflect.Type{ + "Builder": reflect.TypeOf(strings.Builder{}), + } +} diff --git a/src/tool/run/packages/stringsNotGo110.go b/src/tool/run/packages/stringsNotGo110.go new file mode 100644 index 0000000..97e6896 --- /dev/null +++ b/src/tool/run/packages/stringsNotGo110.go @@ -0,0 +1,5 @@ +// +build !go1.10 + +package packages + +func stringsGo110() {} diff --git a/src/tool/run/packages/sync.go b/src/tool/run/packages/sync.go new file mode 100644 index 0000000..7e5e744 --- /dev/null +++ b/src/tool/run/packages/sync.go @@ -0,0 +1,23 @@ +package packages + +import ( + "reflect" + "sync" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["sync"] = map[string]reflect.Value{ + "NewCond": reflect.ValueOf(sync.NewCond), + } + env.PackageTypes["sync"] = map[string]reflect.Type{ + "Cond": reflect.TypeOf(sync.Cond{}), + "Mutex": reflect.TypeOf(sync.Mutex{}), + "Once": reflect.TypeOf(sync.Once{}), + "Pool": reflect.TypeOf(sync.Pool{}), + "RWMutex": reflect.TypeOf(sync.RWMutex{}), + "WaitGroup": reflect.TypeOf(sync.WaitGroup{}), + } + syncGo19() +} diff --git a/src/tool/run/packages/syncGo19.go b/src/tool/run/packages/syncGo19.go new file mode 100644 index 0000000..27ba4d8 --- /dev/null +++ b/src/tool/run/packages/syncGo19.go @@ -0,0 +1,14 @@ +// +build go1.9 + +package packages + +import ( + "reflect" + "sync" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func syncGo19() { + env.PackageTypes["sync"]["Map"] = reflect.TypeOf(sync.Map{}) +} diff --git a/src/tool/run/packages/syncNotGo19.go b/src/tool/run/packages/syncNotGo19.go new file mode 100644 index 0000000..36f3839 --- /dev/null +++ b/src/tool/run/packages/syncNotGo19.go @@ -0,0 +1,5 @@ +// +build !go1.9 + +package packages + +func syncGo19() {} diff --git a/src/tool/run/packages/time.go b/src/tool/run/packages/time.go new file mode 100644 index 0000000..5db7292 --- /dev/null +++ b/src/tool/run/packages/time.go @@ -0,0 +1,75 @@ +package packages + +import ( + "reflect" + "time" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func init() { + env.Packages["time"] = map[string]reflect.Value{ + "ANSIC": reflect.ValueOf(time.ANSIC), + "After": reflect.ValueOf(time.After), + "AfterFunc": reflect.ValueOf(time.AfterFunc), + "April": reflect.ValueOf(time.April), + "August": reflect.ValueOf(time.August), + "Date": reflect.ValueOf(time.Date), + "December": reflect.ValueOf(time.December), + "February": reflect.ValueOf(time.February), + "FixedZone": reflect.ValueOf(time.FixedZone), + "Friday": reflect.ValueOf(time.Friday), + "Hour": reflect.ValueOf(time.Hour), + "January": reflect.ValueOf(time.January), + "July": reflect.ValueOf(time.July), + "June": reflect.ValueOf(time.June), + "Kitchen": reflect.ValueOf(time.Kitchen), + "LoadLocation": reflect.ValueOf(time.LoadLocation), + "March": reflect.ValueOf(time.March), + "May": reflect.ValueOf(time.May), + "Microsecond": reflect.ValueOf(time.Microsecond), + "Millisecond": reflect.ValueOf(time.Millisecond), + "Minute": reflect.ValueOf(time.Minute), + "Monday": reflect.ValueOf(time.Monday), + "Nanosecond": reflect.ValueOf(time.Nanosecond), + "NewTicker": reflect.ValueOf(time.NewTicker), + "NewTimer": reflect.ValueOf(time.NewTimer), + "November": reflect.ValueOf(time.November), + "Now": reflect.ValueOf(time.Now), + "October": reflect.ValueOf(time.October), + "Parse": reflect.ValueOf(time.Parse), + "ParseDuration": reflect.ValueOf(time.ParseDuration), + "ParseInLocation": reflect.ValueOf(time.ParseInLocation), + "RFC1123": reflect.ValueOf(time.RFC1123), + "RFC1123Z": reflect.ValueOf(time.RFC1123Z), + "RFC3339": reflect.ValueOf(time.RFC3339), + "RFC3339Nano": reflect.ValueOf(time.RFC3339Nano), + "RFC822": reflect.ValueOf(time.RFC822), + "RFC822Z": reflect.ValueOf(time.RFC822Z), + "RFC850": reflect.ValueOf(time.RFC850), + "RubyDate": reflect.ValueOf(time.RubyDate), + "Saturday": reflect.ValueOf(time.Saturday), + "Second": reflect.ValueOf(time.Second), + "September": reflect.ValueOf(time.September), + "Since": reflect.ValueOf(time.Since), + "Sleep": reflect.ValueOf(time.Sleep), + "Stamp": reflect.ValueOf(time.Stamp), + "StampMicro": reflect.ValueOf(time.StampMicro), + "StampMilli": reflect.ValueOf(time.StampMilli), + "StampNano": reflect.ValueOf(time.StampNano), + "Sunday": reflect.ValueOf(time.Sunday), + "Thursday": reflect.ValueOf(time.Thursday), + "Tick": reflect.ValueOf(time.Tick), + "Tuesday": reflect.ValueOf(time.Tuesday), + "Unix": reflect.ValueOf(time.Unix), + "UnixDate": reflect.ValueOf(time.UnixDate), + "Wednesday": reflect.ValueOf(time.Wednesday), + } + env.PackageTypes["time"] = map[string]reflect.Type{ + "Duration": reflect.TypeOf(time.Duration(0)), + "Ticker": reflect.TypeOf(time.Ticker{}), + "Time": reflect.TypeOf(time.Time{}), + } + timeGo18() + timeGo110() +} diff --git a/src/tool/run/packages/timeGo110.go b/src/tool/run/packages/timeGo110.go new file mode 100644 index 0000000..3893918 --- /dev/null +++ b/src/tool/run/packages/timeGo110.go @@ -0,0 +1,14 @@ +// +build go1.10 + +package packages + +import ( + "reflect" + "time" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func timeGo110() { + env.Packages["time"]["LoadLocationFromTZData"] = reflect.ValueOf(time.LoadLocationFromTZData) +} diff --git a/src/tool/run/packages/timeGo18.go b/src/tool/run/packages/timeGo18.go new file mode 100644 index 0000000..bb6749a --- /dev/null +++ b/src/tool/run/packages/timeGo18.go @@ -0,0 +1,14 @@ +// +build go1.8 + +package packages + +import ( + "reflect" + "time" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func timeGo18() { + env.Packages["time"]["Until"] = reflect.ValueOf(time.Until) +} diff --git a/src/tool/run/packages/timeNotGo110.go b/src/tool/run/packages/timeNotGo110.go new file mode 100644 index 0000000..bae9c13 --- /dev/null +++ b/src/tool/run/packages/timeNotGo110.go @@ -0,0 +1,5 @@ +// +build !go1.10 + +package packages + +func timeGo110() {} diff --git a/src/tool/run/packages/timeNotGo18.go b/src/tool/run/packages/timeNotGo18.go new file mode 100644 index 0000000..57cbc3e --- /dev/null +++ b/src/tool/run/packages/timeNotGo18.go @@ -0,0 +1,5 @@ +// +build !go1.8 + +package packages + +func timeGo18() {} diff --git a/src/tool/run/parser/Makefile b/src/tool/run/parser/Makefile new file mode 100644 index 0000000..72c8b09 --- /dev/null +++ b/src/tool/run/parser/Makefile @@ -0,0 +1,5 @@ +all : parser.go + +parser.go : parser.go.y + goyacc -o $@ parser.go.y + gofmt -s -w . diff --git a/src/tool/run/parser/lexer.go b/src/tool/run/parser/lexer.go new file mode 100644 index 0000000..6bf3869 --- /dev/null +++ b/src/tool/run/parser/lexer.go @@ -0,0 +1,653 @@ +// Package parser implements parser for anko. +package parser + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" + "unicode" + + "github.com/surdeus/goblin/src/tool/run/ast" +) + +const ( + // EOF is short for End of file. + EOF = -1 + // EOL is short for End of line. + EOL = '\n' +) + +// Error is a parse error. +type Error struct { + Message string + Pos ast.Position + Filename string + Fatal bool +} + +// Error returns the parse error message. +func (e *Error) Error() string { + return e.Message +} + +// Scanner stores informations for lexer. +type Scanner struct { + src []rune + offset int + lineHead int + line int +} + +// opName is correction of operation names. +var opName = map[string]int{ + "func": FUNC, + "return": RETURN, + "var": VAR, + "throw": THROW, + "if": IF, + "for": FOR, + "break": BREAK, + "continue": CONTINUE, + "in": IN, + "else": ELSE, + "new": NEW, + "true": TRUE, + "false": FALSE, + "nil": NIL, + "module": MODULE, + "try": TRY, + "catch": CATCH, + "finally": FINALLY, + "switch": SWITCH, + "case": CASE, + "default": DEFAULT, + "go": GO, + "chan": CHAN, + "struct": STRUCT, + "make": MAKE, + "type": TYPE, + "len": LEN, + "delete": DELETE, + "close": CLOSE, + "map": MAP, + "import": IMPORT, +} + +var ( + nilValue = reflect.New(reflect.TypeOf((*interface{})(nil)).Elem()).Elem() + trueValue = reflect.ValueOf(true) + falseValue = reflect.ValueOf(false) + oneLiteral = &ast.LiteralExpr{Literal: reflect.ValueOf(int64(1))} +) + +// Init resets code to scan. +func (s *Scanner) Init(src string) { + s.src = []rune(src) +} + +// Scan analyses token, and decide identify or literals. +func (s *Scanner) Scan() (tok int, lit string, pos ast.Position, err error) { +retry: + s.skipBlank() + pos = s.pos() + switch ch := s.peek(); { + case isLetter(ch): + lit, err = s.scanIdentifier() + if err != nil { + return + } + if name, ok := opName[lit]; ok { + tok = name + } else { + tok = IDENT + } + case isDigit(ch): + tok = NUMBER + lit, err = s.scanNumber() + if err != nil { + return + } + case ch == '"': + tok = STRING + lit, err = s.scanString('"') + if err != nil { + return + } + case ch == '\'': + tok = STRING + lit, err = s.scanString('\'') + if err != nil { + return + } + case ch == '`': + tok = STRING + lit, err = s.scanRawString('`') + if err != nil { + return + } + default: + switch ch { + case EOF: + tok = EOF + case '#': + for !isEOL(s.peek()) { + s.next() + } + goto retry + case '!': + s.next() + switch s.peek() { + case '=': + tok = NEQ + lit = "!=" + default: + s.back() + tok = int(ch) + lit = string(ch) + } + case '=': + s.next() + switch s.peek() { + case '=': + tok = EQEQ + lit = "==" + case ' ': + if s.peekPlus(1) == '<' && s.peekPlus(2) == '-' { + s.next() + s.next() + tok = EQOPCHAN + lit = "= <-" + } else { + s.back() + tok = int(ch) + lit = string(ch) + } + default: + s.back() + tok = int(ch) + lit = string(ch) + } + case '?': + s.next() + switch s.peek() { + case '?': + tok = NILCOALESCE + lit = "??" + default: + s.back() + tok = int(ch) + lit = string(ch) + } + case '+': + s.next() + switch s.peek() { + case '+': + tok = PLUSPLUS + lit = "++" + case '=': + tok = PLUSEQ + lit = "+=" + default: + s.back() + tok = int(ch) + lit = string(ch) + } + case '-': + s.next() + switch s.peek() { + case '-': + tok = MINUSMINUS + lit = "--" + case '=': + tok = MINUSEQ + lit = "-=" + default: + s.back() + tok = int(ch) + lit = "-" + } + case '*': + s.next() + switch s.peek() { + case '=': + tok = MULEQ + lit = "*=" + default: + s.back() + tok = int(ch) + lit = string(ch) + } + case '/': + s.next() + switch s.peek() { + case '=': + tok = DIVEQ + lit = "/=" + case '/': + for !isEOL(s.peek()) { + s.next() + } + goto retry + case '*': + for { + _, err = s.scanRawString('*') + if err != nil { + return + } + + if s.peek() == '/' { + s.next() + goto retry + } + + s.back() + } + default: + s.back() + tok = int(ch) + lit = string(ch) + } + case '>': + s.next() + switch s.peek() { + case '=': + tok = GE + lit = ">=" + case '>': + tok = SHIFTRIGHT + lit = ">>" + default: + s.back() + tok = int(ch) + lit = string(ch) + } + case '<': + s.next() + switch s.peek() { + case '-': + tok = OPCHAN + lit = "<-" + case '=': + tok = LE + lit = "<=" + case '<': + tok = SHIFTLEFT + lit = "<<" + default: + s.back() + tok = int(ch) + lit = string(ch) + } + case '|': + s.next() + switch s.peek() { + case '|': + tok = OROR + lit = "||" + case '=': + tok = OREQ + lit = "|=" + default: + s.back() + tok = int(ch) + lit = string(ch) + } + case '&': + s.next() + switch s.peek() { + case '&': + tok = ANDAND + lit = "&&" + case '=': + tok = ANDEQ + lit = "&=" + default: + s.back() + tok = int(ch) + lit = string(ch) + } + case '.': + s.next() + if s.peek() == '.' { + s.next() + if s.peek() == '.' { + tok = VARARG + } else { + err = fmt.Errorf("syntax error on '%v' at %v:%v", string(ch), pos.Line, pos.Column) + return + } + } else { + s.back() + tok = int(ch) + lit = string(ch) + } + case '\n', '(', ')', ':', ';', '%', '{', '}', '[', ']', ',', '^': + tok = int(ch) + lit = string(ch) + default: + err = fmt.Errorf("syntax error on '%v' at %v:%v", string(ch), pos.Line, pos.Column) + tok = int(ch) + lit = string(ch) + return + } + s.next() + } + return +} + +// isLetter returns true if the rune is a letter for identity. +func isLetter(ch rune) bool { + return unicode.IsLetter(ch) || ch == '_' +} + +// isDigit returns true if the rune is a number. +func isDigit(ch rune) bool { + return '0' <= ch && ch <= '9' +} + +// isHex returns true if the rune is a hex digits. +func isHex(ch rune) bool { + return ('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F') +} + +// isEOL returns true if the rune is at end-of-line or end-of-file. +func isEOL(ch rune) bool { + return ch == '\n' || ch == -1 +} + +// isBlank returns true if the rune is empty character.. +func isBlank(ch rune) bool { + return ch == ' ' || ch == '\t' || ch == '\r' +} + +// peek returns current rune in the code. +func (s *Scanner) peek() rune { + if s.reachEOF() { + return EOF + } + return s.src[s.offset] +} + +// peek returns current rune plus i in the code. +func (s *Scanner) peekPlus(i int) rune { + if len(s.src) <= s.offset+i { + return EOF + } + return s.src[s.offset+i] +} + +// next moves offset to next. +func (s *Scanner) next() { + if !s.reachEOF() { + if s.peek() == '\n' { + s.lineHead = s.offset + 1 + s.line++ + } + s.offset++ + } +} + +// current returns the current offset. +func (s *Scanner) current() int { + return s.offset +} + +// offset sets the offset value. +func (s *Scanner) set(o int) { + s.offset = o +} + +// back moves back offset once to top. +func (s *Scanner) back() { + s.offset-- +} + +// reachEOF returns true if offset is at end-of-file. +func (s *Scanner) reachEOF() bool { + return len(s.src) <= s.offset +} + +// pos returns the position of current. +func (s *Scanner) pos() ast.Position { + return ast.Position{Line: s.line + 1, Column: s.offset - s.lineHead + 1} +} + +// skipBlank moves position into non-black character. +func (s *Scanner) skipBlank() { + for isBlank(s.peek()) { + s.next() + } +} + +// scanIdentifier returns identifier beginning at current position. +func (s *Scanner) scanIdentifier() (string, error) { + var ret []rune + for { + if !isLetter(s.peek()) && !isDigit(s.peek()) { + break + } + ret = append(ret, s.peek()) + s.next() + } + return string(ret), nil +} + +// scanNumber returns number beginning at current position. +func (s *Scanner) scanNumber() (string, error) { + result := []rune{s.peek()} + s.next() + + if result[0] == '0' && (s.peek() == 'x' || s.peek() == 'X') { + // hex + result = append(result, 'x') + s.next() + for isHex(s.peek()) { + result = append(result, s.peek()) + s.next() + } + } else { + // non-hex + found := false + for { + if isDigit(s.peek()) { + // is digit + result = append(result, s.peek()) + s.next() + continue + } + + if s.peek() == '.' { + // is . + result = append(result, '.') + s.next() + continue + } + + if s.peek() == 'e' || s.peek() == 'E' { + // is e + if found { + return "", errors.New("unexpected " + string(s.peek())) + } + found = true + s.next() + + // check if + or - + if s.peek() == '+' || s.peek() == '-' { + // add e with + or - + result = append(result, 'e') + result = append(result, s.peek()) + s.next() + } else { + // add e, but next char not + or - + result = append(result, 'e') + } + continue + } + + // not digit, e, nor . + break + } + } + + if isLetter(s.peek()) { + return "", errors.New("identifier starts immediately after numeric literal") + } + + return string(result), nil +} + +// scanRawString returns raw-string starting at current position. +func (s *Scanner) scanRawString(l rune) (string, error) { + var ret []rune + for { + s.next() + if s.peek() == EOF { + return "", errors.New("unexpected EOF") + } + if s.peek() == l { + s.next() + break + } + ret = append(ret, s.peek()) + } + return string(ret), nil +} + +// scanString returns string starting at current position. +// This handles backslash escaping. +func (s *Scanner) scanString(l rune) (string, error) { + var ret []rune +eos: + for { + s.next() + switch s.peek() { + case EOL: + return "", errors.New("unexpected EOL") + case EOF: + return "", errors.New("unexpected EOF") + case l: + s.next() + break eos + case '\\': + s.next() + switch s.peek() { + case 'b': + ret = append(ret, '\b') + continue + case 'f': + ret = append(ret, '\f') + continue + case 'r': + ret = append(ret, '\r') + continue + case 'n': + ret = append(ret, '\n') + continue + case 't': + ret = append(ret, '\t') + continue + } + ret = append(ret, s.peek()) + continue + default: + ret = append(ret, s.peek()) + } + } + return string(ret), nil +} + +// Lexer provides interface to parse codes. +type Lexer struct { + s *Scanner + lit string + pos ast.Position + e error + stmt ast.Stmt +} + +// Lex scans the token and literals. +func (l *Lexer) Lex(lval *yySymType) int { + tok, lit, pos, err := l.s.Scan() + if err != nil { + l.e = &Error{Message: err.Error(), Pos: pos, Fatal: true} + } + lval.tok = ast.Token{Tok: tok, Lit: lit} + lval.tok.SetPosition(pos) + l.lit = lit + l.pos = pos + return tok +} + +// Error sets parse error. +func (l *Lexer) Error(msg string) { + l.e = &Error{Message: msg, Pos: l.pos, Fatal: false} +} + +// Parse provides way to parse the code using Scanner. +func Parse(s *Scanner) (ast.Stmt, error) { + l := Lexer{s: s} + if yyParse(&l) != 0 { + return nil, l.e + } + return l.stmt, l.e +} + +// EnableErrorVerbose enabled verbose errors from the parser +func EnableErrorVerbose() { + yyErrorVerbose = true +} + +// EnableDebug enabled debug from the parser +func EnableDebug(level int) { + yyDebug = level +} + +// ParseSrc provides way to parse the code from source. +func ParseSrc(src string) (ast.Stmt, error) { + scanner := &Scanner{ + src: []rune(src), + } + return Parse(scanner) +} + +func toNumber(numString string) (reflect.Value, error) { + // hex + if len(numString) > 2 && numString[0:2] == "0x" { + i, err := strconv.ParseInt(numString[2:], 16, 64) + if err != nil { + return nilValue, err + } + return reflect.ValueOf(i), nil + } + + // hex + if len(numString) > 3 && numString[0:3] == "-0x" { + i, err := strconv.ParseInt("-"+numString[3:], 16, 64) + if err != nil { + return nilValue, err + } + return reflect.ValueOf(i), nil + } + + // float + if strings.Contains(numString, ".") || strings.Contains(numString, "e") { + f, err := strconv.ParseFloat(numString, 64) + if err != nil { + return nilValue, err + } + return reflect.ValueOf(f), nil + } + + // int + i, err := strconv.ParseInt(numString, 10, 64) + if err != nil { + return nilValue, err + } + return reflect.ValueOf(i), nil +} + +func stringToValue(aString string) reflect.Value { + return reflect.ValueOf(aString) +} diff --git a/src/tool/run/parser/parser.go b/src/tool/run/parser/parser.go new file mode 100644 index 0000000..aecf8a2 --- /dev/null +++ b/src/tool/run/parser/parser.go @@ -0,0 +1,2483 @@ +// Code generated by goyacc -o parser.go parser.go.y. DO NOT EDIT. + +//line parser.go.y:2 +package parser + +import __yyfmt__ "fmt" + +//line parser.go.y:2 + +import ( + "github.com/surdeus/goblin/src/tool/run/ast" +) + +//line parser.go.y:45 +type yySymType struct { + yys int + tok ast.Token + + compstmt ast.Stmt + stmts ast.Stmt + stmt ast.Stmt + stmt_var_or_lets ast.Stmt + stmt_var ast.Stmt + stmt_lets ast.Stmt + stmt_if ast.Stmt + stmt_for ast.Stmt + stmt_switch ast.Stmt + stmt_switch_cases ast.Stmt + stmt_switch_case ast.Stmt + stmt_switch_default ast.Stmt + + exprs []ast.Expr + expr ast.Expr + expr_idents []string + type_data *ast.TypeStruct + type_data_struct *ast.TypeStruct + slice_count int + expr_member_or_ident ast.Expr + expr_member *ast.MemberExpr + expr_ident *ast.IdentExpr + expr_literals ast.Expr + expr_map *ast.MapExpr + expr_slice ast.Expr + expr_chan ast.Expr + expr_unary ast.Expr + expr_binary ast.Expr + expr_lets ast.Expr + + op_binary ast.Operator + op_comparison ast.Operator + op_add ast.Operator + op_multiply ast.Operator +} + +const IDENT = 57346 +const NUMBER = 57347 +const STRING = 57348 +const ARRAY = 57349 +const VARARG = 57350 +const FUNC = 57351 +const RETURN = 57352 +const VAR = 57353 +const THROW = 57354 +const IF = 57355 +const ELSE = 57356 +const FOR = 57357 +const IN = 57358 +const EQEQ = 57359 +const NEQ = 57360 +const GE = 57361 +const LE = 57362 +const OROR = 57363 +const ANDAND = 57364 +const NEW = 57365 +const TRUE = 57366 +const FALSE = 57367 +const NIL = 57368 +const NILCOALESCE = 57369 +const MODULE = 57370 +const TRY = 57371 +const CATCH = 57372 +const FINALLY = 57373 +const PLUSEQ = 57374 +const MINUSEQ = 57375 +const MULEQ = 57376 +const DIVEQ = 57377 +const ANDEQ = 57378 +const OREQ = 57379 +const BREAK = 57380 +const CONTINUE = 57381 +const PLUSPLUS = 57382 +const MINUSMINUS = 57383 +const SHIFTLEFT = 57384 +const SHIFTRIGHT = 57385 +const SWITCH = 57386 +const CASE = 57387 +const DEFAULT = 57388 +const GO = 57389 +const CHAN = 57390 +const STRUCT = 57391 +const MAKE = 57392 +const OPCHAN = 57393 +const EQOPCHAN = 57394 +const TYPE = 57395 +const LEN = 57396 +const DELETE = 57397 +const CLOSE = 57398 +const MAP = 57399 +const IMPORT = 57400 +const UNARY = 57401 + +var yyToknames = [...]string{ + "$end", + "error", + "$unk", + "IDENT", + "NUMBER", + "STRING", + "ARRAY", + "VARARG", + "FUNC", + "RETURN", + "VAR", + "THROW", + "IF", + "ELSE", + "FOR", + "IN", + "EQEQ", + "NEQ", + "GE", + "LE", + "OROR", + "ANDAND", + "NEW", + "TRUE", + "FALSE", + "NIL", + "NILCOALESCE", + "MODULE", + "TRY", + "CATCH", + "FINALLY", + "PLUSEQ", + "MINUSEQ", + "MULEQ", + "DIVEQ", + "ANDEQ", + "OREQ", + "BREAK", + "CONTINUE", + "PLUSPLUS", + "MINUSMINUS", + "SHIFTLEFT", + "SHIFTRIGHT", + "SWITCH", + "CASE", + "DEFAULT", + "GO", + "CHAN", + "STRUCT", + "MAKE", + "OPCHAN", + "EQOPCHAN", + "TYPE", + "LEN", + "DELETE", + "CLOSE", + "MAP", + "IMPORT", + "'='", + "':'", + "'?'", + "'<'", + "'>'", + "'+'", + "'-'", + "'|'", + "'^'", + "'*'", + "'/'", + "'%'", + "'&'", + "UNARY", + "'{'", + "'}'", + "'('", + "')'", + "','", + "';'", + "'['", + "']'", + "'.'", + "'!'", + "'\\n'", +} +var yyStatenames = [...]string{} + +const yyEofCode = 1 +const yyErrCode = 2 +const yyInitialStackSize = 16 + +//line parser.go.y:1089 + +//line yacctab:1 +var yyExca = [...]int{ + -1, 1, + 1, -1, + -2, 0, + -1, 2, + 52, 57, + 59, 57, + 77, 57, + 78, 5, + -2, 1, + -1, 23, + 77, 58, + -2, 26, + -1, 27, + 16, 95, + -2, 57, + -1, 67, + 52, 57, + 59, 57, + 77, 57, + -2, 5, + -1, 120, + 16, 96, + 77, 96, + -2, 112, + -1, 124, + 4, 107, + 48, 107, + 49, 107, + 57, 107, + -2, 69, + -1, 267, + 74, 181, + 80, 181, + -2, 173, + -1, 288, + 74, 181, + -2, 173, + -1, 292, + 1, 60, + 8, 60, + 45, 60, + 46, 60, + 52, 60, + 59, 60, + 60, 60, + 74, 60, + 76, 60, + 77, 60, + 78, 60, + 80, 60, + 83, 60, + -2, 110, + -1, 296, + 1, 17, + 45, 17, + 46, 17, + 74, 17, + 78, 17, + 83, 17, + -2, 74, + -1, 298, + 1, 19, + 45, 19, + 46, 19, + 74, 19, + 78, 19, + 83, 19, + -2, 76, + -1, 328, + 74, 179, + 80, 179, + -2, 174, + -1, 348, + 1, 16, + 45, 16, + 46, 16, + 74, 16, + 78, 16, + 83, 16, + -2, 73, + -1, 349, + 1, 18, + 45, 18, + 46, 18, + 74, 18, + 78, 18, + 83, 18, + -2, 75, +} + +const yyPrivate = 57344 + +const yyLast = 3907 + +var yyAct = [...]int{ + + 71, 268, 33, 23, 232, 320, 321, 36, 116, 323, + 322, 5, 8, 288, 377, 72, 8, 267, 76, 8, + 8, 124, 386, 8, 8, 127, 281, 114, 117, 121, + 214, 214, 282, 283, 84, 135, 130, 214, 85, 214, + 87, 1, 331, 214, 207, 286, 214, 8, 7, 329, + 220, 137, 151, 327, 134, 69, 49, 145, 152, 153, + 154, 155, 156, 213, 217, 202, 142, 373, 23, 149, + 70, 214, 143, 404, 340, 349, 348, 334, 162, 163, + 325, 166, 167, 168, 169, 297, 171, 173, 303, 175, + 149, 210, 176, 177, 178, 179, 180, 181, 182, 183, + 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, + 194, 195, 196, 197, 198, 136, 159, 69, 295, 206, + 448, 326, 203, 148, 234, 160, 203, 211, 141, 274, + 147, 209, 130, 130, 140, 130, 86, 139, 223, 225, + 226, 170, 203, 130, 130, 233, 130, 265, 149, 236, + 123, 138, 447, 298, 149, 215, 216, 200, 218, 144, + 89, 90, 100, 101, 78, 245, 227, 228, 77, 231, + 443, 248, 438, 252, 437, 69, 435, 428, 427, 6, + 423, 422, 212, 128, 346, 68, 296, 149, 97, 98, + 99, 102, 125, 222, 367, 84, 421, 275, 149, 85, + 238, 87, 419, 255, 235, 86, 259, 246, 262, 410, + 128, 409, 126, 164, 405, 264, 203, 240, 241, 266, + 130, 122, 126, 211, 401, 278, 397, 132, 133, 89, + 90, 233, 229, 395, 287, 285, 131, 444, 291, 249, + 149, 253, 394, 272, 299, 292, 257, 129, 302, 393, + 69, 390, 304, 347, 132, 133, 385, 356, 134, 343, + 311, 315, 317, 131, 84, 308, 301, 293, 85, 254, + 87, 239, 442, 165, 129, 408, 388, 372, 335, 9, + 371, 324, 219, 158, 339, 134, 74, 341, 441, 130, + 345, 290, 323, 322, 436, 201, 294, 79, 430, 333, + 312, 310, 230, 10, 284, 271, 146, 174, 354, 237, + 73, 4, 342, 2, 62, 67, 63, 66, 69, 363, + 64, 65, 126, 47, 368, 366, 365, 330, 46, 126, + 45, 119, 374, 44, 130, 43, 130, 30, 50, 381, + 29, 384, 351, 332, 319, 387, 22, 157, 21, 20, + 25, 355, 24, 3, 391, 357, 358, 375, 360, 378, + 0, 0, 0, 256, 0, 0, 370, 0, 263, 0, + 0, 0, 0, 270, 0, 0, 0, 69, 0, 273, + 369, 412, 0, 126, 414, 0, 0, 0, 126, 389, + 0, 0, 269, 126, 0, 0, 0, 0, 0, 126, + 0, 396, 0, 398, 399, 0, 0, 0, 0, 402, + 0, 0, 269, 406, 407, 0, 0, 233, 434, 0, + 0, 433, 0, 0, 0, 0, 0, 237, 0, 0, + 418, 0, 0, 130, 0, 440, 0, 0, 201, 0, + 0, 0, 424, 0, 0, 425, 426, 328, 344, 0, + 429, 0, 0, 0, 0, 0, 439, 0, 0, 0, + 0, 0, 0, 0, 0, 269, 0, 0, 328, 120, + 52, 53, 0, 0, 31, 0, 48, 0, 0, 364, + 0, 0, 0, 0, 445, 0, 446, 0, 39, 54, + 55, 56, 376, 0, 201, 0, 201, 0, 0, 126, + 86, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 269, 0, 126, 0, 0, 40, 57, 0, 0, 37, + 0, 0, 41, 38, 89, 90, 100, 101, 0, 0, + 51, 0, 59, 61, 0, 0, 60, 411, 115, 0, + 34, 0, 201, 118, 32, 0, 416, 58, 103, 104, + 105, 0, 97, 98, 99, 102, 0, 126, 0, 84, + 0, 0, 0, 85, 0, 87, 126, 0, 0, 86, + 106, 107, 111, 109, 113, 112, 0, 0, 0, 0, + 83, 0, 0, 0, 0, 91, 92, 94, 95, 96, + 93, 0, 0, 89, 90, 100, 101, 0, 0, 0, + 0, 0, 0, 0, 88, 81, 0, 0, 0, 0, + 0, 0, 80, 269, 82, 108, 110, 103, 104, 105, + 0, 97, 98, 99, 102, 0, 204, 0, 84, 0, + 0, 0, 85, 0, 87, 35, 52, 53, 0, 0, + 31, 13, 48, 14, 26, 0, 27, 0, 0, 0, + 0, 0, 0, 0, 39, 54, 55, 56, 0, 15, + 16, 0, 0, 0, 0, 0, 0, 0, 0, 11, + 12, 0, 0, 0, 0, 28, 0, 0, 17, 0, + 0, 40, 57, 0, 0, 37, 18, 19, 41, 38, + 0, 0, 0, 0, 0, 0, 51, 0, 59, 61, + 0, 0, 60, 0, 42, 0, 34, 0, 0, 0, + 32, 0, 0, 58, 86, 106, 107, 111, 109, 113, + 112, 0, 0, 0, 0, 83, 0, 0, 0, 0, + 91, 92, 94, 95, 96, 93, 0, 0, 89, 90, + 100, 101, 0, 0, 0, 0, 0, 0, 0, 88, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, + 108, 110, 103, 104, 105, 0, 97, 98, 99, 102, + 0, 0, 0, 84, 382, 383, 0, 85, 0, 87, + 86, 106, 107, 111, 109, 113, 112, 0, 0, 0, + 0, 83, 0, 0, 0, 0, 91, 92, 94, 95, + 96, 93, 0, 0, 89, 90, 100, 101, 0, 0, + 0, 0, 0, 0, 0, 88, 0, 0, 0, 0, + 0, 0, 0, 0, 380, 82, 108, 110, 103, 104, + 105, 0, 97, 98, 99, 102, 0, 0, 0, 84, + 0, 0, 0, 85, 379, 87, 86, 106, 107, 111, + 109, 113, 112, 0, 0, 0, 0, 83, 0, 0, + 0, 0, 91, 92, 94, 95, 96, 93, 0, 0, + 89, 90, 100, 101, 0, 0, 0, 0, 0, 0, + 0, 88, 0, 0, 0, 0, 0, 0, 0, 0, + 353, 82, 108, 110, 103, 104, 105, 0, 97, 98, + 99, 102, 0, 0, 0, 84, 0, 0, 0, 85, + 352, 87, 86, 106, 107, 111, 109, 113, 112, 0, + 0, 0, 0, 83, 0, 0, 0, 0, 91, 92, + 94, 95, 96, 93, 0, 0, 89, 90, 100, 101, + 0, 0, 0, 0, 0, 0, 0, 88, 0, 0, + 0, 0, 0, 0, 0, 0, 338, 82, 108, 110, + 103, 104, 105, 0, 97, 98, 99, 102, 0, 0, + 0, 84, 0, 0, 0, 85, 337, 87, 86, 106, + 107, 111, 109, 113, 112, 0, 0, 0, 0, 83, + 0, 0, 0, 0, 91, 92, 94, 95, 96, 93, + 0, 0, 89, 90, 100, 101, 0, 0, 0, 0, + 0, 0, 0, 88, 0, 0, 0, 0, 0, 0, + 0, 0, 307, 82, 108, 110, 103, 104, 105, 0, + 97, 98, 99, 102, 0, 0, 0, 84, 0, 0, + 0, 85, 306, 87, 86, 106, 107, 111, 109, 113, + 112, 0, 0, 0, 0, 83, 0, 0, 0, 0, + 91, 92, 94, 95, 96, 93, 0, 0, 89, 90, + 100, 101, 0, 0, 0, 0, 0, 0, 0, 88, + 0, 0, 0, 0, 0, 0, 0, 0, 277, 82, + 108, 110, 103, 104, 105, 0, 97, 98, 99, 102, + 0, 0, 0, 84, 0, 0, 0, 85, 276, 87, + 86, 106, 107, 111, 109, 113, 112, 0, 0, 0, + 0, 83, 0, 0, 0, 0, 91, 92, 94, 95, + 96, 93, 0, 0, 89, 90, 100, 101, 0, 0, + 0, 0, 0, 0, 0, 88, 0, 0, 0, 0, + 0, 0, 0, 0, 251, 82, 108, 110, 103, 104, + 105, 0, 97, 98, 99, 102, 0, 0, 0, 84, + 0, 0, 0, 85, 250, 87, 86, 106, 107, 111, + 109, 113, 112, 0, 0, 0, 0, 83, 0, 0, + 0, 0, 91, 92, 94, 95, 96, 93, 0, 0, + 89, 90, 100, 101, 0, 0, 0, 0, 0, 0, + 0, 88, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 82, 108, 110, 103, 104, 105, 0, 97, 98, + 99, 102, 0, 0, 0, 84, 242, 243, 0, 85, + 0, 87, 86, 106, 107, 111, 109, 113, 112, 0, + 0, 0, 0, 83, 0, 0, 0, 0, 91, 92, + 94, 95, 96, 93, 0, 0, 89, 90, 100, 101, + 0, 0, 0, 0, 0, 0, 0, 88, 81, 0, + 0, 0, 0, 0, 0, 80, 0, 82, 108, 110, + 103, 104, 105, 0, 97, 98, 99, 102, 0, 0, + 0, 84, 0, 0, 0, 85, 0, 87, 86, 106, + 107, 111, 109, 113, 112, 0, 0, 0, 0, 83, + 0, 0, 0, 0, 91, 92, 94, 95, 96, 93, + 0, 0, 89, 90, 100, 101, 0, 0, 0, 0, + 0, 0, 0, 88, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 82, 108, 110, 103, 104, 105, 0, + 97, 98, 99, 102, 0, 0, 0, 84, 432, 0, + 0, 85, 0, 87, 86, 106, 107, 111, 109, 113, + 112, 0, 0, 0, 0, 83, 0, 0, 0, 0, + 91, 92, 94, 95, 96, 93, 0, 0, 89, 90, + 100, 101, 0, 0, 0, 0, 0, 0, 0, 88, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, + 108, 110, 103, 104, 105, 0, 97, 98, 99, 102, + 0, 0, 0, 84, 0, 0, 0, 85, 431, 87, + 86, 106, 107, 111, 109, 113, 112, 0, 0, 0, + 0, 83, 0, 0, 0, 0, 91, 92, 94, 95, + 96, 93, 0, 0, 89, 90, 100, 101, 0, 0, + 0, 0, 0, 0, 0, 88, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 82, 108, 110, 103, 104, + 105, 0, 97, 98, 99, 102, 0, 0, 0, 84, + 0, 0, 0, 85, 420, 87, 86, 106, 107, 111, + 109, 113, 112, 0, 0, 0, 0, 83, 0, 0, + 0, 0, 91, 92, 94, 95, 96, 93, 0, 0, + 89, 90, 100, 101, 0, 0, 0, 0, 0, 0, + 0, 88, 0, 0, 0, 0, 0, 0, 0, 0, + 417, 82, 108, 110, 103, 104, 105, 0, 97, 98, + 99, 102, 0, 0, 0, 84, 0, 0, 0, 85, + 0, 87, 86, 106, 107, 111, 109, 113, 112, 0, + 0, 0, 0, 83, 0, 0, 0, 0, 91, 92, + 94, 95, 96, 93, 0, 0, 89, 90, 100, 101, + 0, 0, 0, 0, 0, 0, 0, 88, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 82, 108, 110, + 103, 104, 105, 0, 97, 98, 99, 102, 0, 0, + 0, 84, 415, 0, 0, 85, 0, 87, 86, 106, + 107, 111, 109, 113, 112, 0, 0, 0, 0, 83, + 0, 0, 0, 0, 91, 92, 94, 95, 96, 93, + 0, 0, 89, 90, 100, 101, 0, 0, 0, 0, + 0, 0, 0, 88, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 82, 108, 110, 103, 104, 105, 0, + 97, 98, 99, 102, 0, 0, 0, 84, 0, 0, + 0, 85, 413, 87, 86, 106, 107, 111, 109, 113, + 112, 0, 0, 0, 0, 83, 0, 0, 0, 0, + 91, 92, 94, 95, 96, 93, 0, 0, 89, 90, + 100, 101, 0, 0, 0, 0, 0, 0, 0, 88, + 0, 0, 0, 0, 0, 0, 0, 0, 403, 82, + 108, 110, 103, 104, 105, 0, 97, 98, 99, 102, + 0, 0, 0, 84, 0, 0, 0, 85, 0, 87, + 86, 106, 107, 111, 109, 113, 112, 0, 0, 0, + 0, 83, 0, 0, 0, 0, 91, 92, 94, 95, + 96, 93, 0, 0, 89, 90, 100, 101, 0, 0, + 0, 0, 0, 0, 0, 88, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 82, 108, 110, 103, 104, + 105, 0, 97, 98, 99, 102, 0, 400, 0, 84, + 0, 0, 0, 85, 0, 87, 86, 106, 107, 111, + 109, 113, 112, 0, 0, 0, 0, 83, 0, 0, + 0, 0, 91, 92, 94, 95, 96, 93, 0, 0, + 89, 90, 100, 101, 0, 0, 0, 0, 0, 0, + 0, 88, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 82, 108, 110, 103, 104, 105, 0, 97, 98, + 99, 102, 0, 0, 0, 84, 0, 0, 0, 85, + 392, 87, 86, 106, 107, 111, 109, 113, 112, 0, + 0, 0, 0, 83, 0, 0, 0, 0, 91, 92, + 94, 95, 96, 93, 0, 0, 89, 90, 100, 101, + 0, 0, 0, 0, 0, 0, 0, 88, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 82, 108, 110, + 103, 104, 105, 0, 97, 98, 99, 102, 0, 361, + 0, 84, 0, 0, 0, 85, 0, 87, 86, 106, + 107, 111, 109, 113, 112, 0, 0, 0, 0, 83, + 0, 0, 0, 0, 91, 92, 94, 95, 96, 93, + 0, 0, 89, 90, 100, 101, 0, 0, 0, 0, + 0, 0, 0, 88, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 82, 108, 110, 103, 104, 105, 0, + 97, 98, 99, 102, 0, 359, 0, 84, 0, 0, + 0, 85, 0, 87, 86, 106, 107, 111, 109, 113, + 112, 0, 0, 0, 0, 83, 0, 0, 0, 0, + 91, 92, 94, 95, 96, 93, 0, 0, 89, 90, + 100, 101, 0, 0, 0, 0, 0, 0, 0, 88, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, + 108, 110, 103, 104, 105, 0, 97, 98, 99, 102, + 0, 0, 0, 84, 350, 0, 0, 85, 0, 87, + 86, 106, 107, 111, 109, 113, 112, 0, 0, 0, + 0, 83, 0, 0, 0, 0, 91, 92, 94, 95, + 96, 93, 0, 0, 89, 90, 100, 101, 0, 0, + 0, 0, 0, 0, 0, 88, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 82, 108, 110, 103, 104, + 105, 0, 97, 98, 99, 102, 0, 0, 0, 84, + 0, 0, 318, 85, 0, 87, 86, 106, 107, 111, + 109, 113, 112, 0, 0, 0, 0, 83, 0, 0, + 0, 0, 91, 92, 94, 95, 96, 93, 0, 0, + 89, 90, 100, 101, 0, 0, 0, 0, 0, 0, + 0, 88, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 82, 108, 110, 103, 104, 105, 0, 97, 98, + 99, 102, 0, 313, 0, 84, 0, 0, 0, 85, + 0, 87, 86, 106, 107, 111, 109, 113, 112, 0, + 0, 0, 0, 83, 0, 0, 0, 0, 91, 92, + 94, 95, 96, 93, 0, 0, 89, 90, 100, 101, + 0, 0, 0, 0, 0, 0, 0, 88, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 82, 108, 110, + 103, 104, 105, 0, 97, 98, 99, 102, 0, 309, + 0, 84, 0, 0, 0, 85, 0, 87, 86, 106, + 107, 111, 109, 113, 112, 0, 0, 0, 0, 83, + 0, 0, 0, 0, 91, 92, 94, 95, 96, 93, + 0, 0, 89, 90, 100, 101, 0, 0, 0, 0, + 0, 0, 0, 88, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 82, 108, 110, 103, 104, 105, 0, + 97, 98, 99, 102, 0, 300, 0, 84, 0, 0, + 0, 85, 0, 87, 86, 106, 107, 111, 109, 113, + 112, 0, 0, 0, 0, 83, 0, 0, 0, 0, + 91, 92, 94, 95, 96, 93, 0, 0, 89, 90, + 100, 101, 0, 0, 0, 0, 0, 0, 0, 88, + 0, 0, 0, 0, 0, 0, 0, 0, 289, 82, + 108, 110, 103, 104, 105, 0, 97, 98, 99, 102, + 0, 0, 0, 84, 0, 0, 0, 85, 0, 87, + 86, 106, 107, 111, 109, 113, 112, 0, 0, 0, + 0, 83, 0, 0, 0, 0, 91, 92, 94, 95, + 96, 93, 0, 0, 89, 90, 100, 101, 0, 0, + 0, 0, 0, 0, 0, 88, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 82, 108, 110, 103, 104, + 105, 0, 97, 98, 99, 102, 0, 0, 0, 84, + 280, 0, 0, 85, 0, 87, 86, 106, 107, 111, + 109, 113, 112, 0, 0, 0, 0, 83, 0, 0, + 0, 0, 91, 92, 94, 95, 96, 93, 0, 0, + 89, 90, 100, 101, 0, 0, 0, 0, 0, 0, + 0, 88, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 82, 108, 110, 103, 104, 105, 0, 97, 98, + 99, 102, 0, 0, 0, 84, 279, 0, 0, 85, + 0, 87, 86, 106, 107, 111, 109, 113, 112, 0, + 0, 0, 0, 83, 0, 0, 0, 0, 91, 92, + 94, 95, 96, 93, 0, 0, 89, 90, 100, 101, + 0, 0, 0, 0, 0, 0, 0, 88, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 82, 108, 110, + 103, 104, 105, 0, 97, 98, 99, 102, 0, 0, + 0, 84, 0, 0, 260, 85, 0, 87, 86, 106, + 107, 111, 109, 113, 112, 0, 0, 0, 0, 83, + 0, 0, 0, 0, 91, 92, 94, 95, 96, 93, + 0, 0, 89, 90, 100, 101, 0, 0, 0, 0, + 0, 0, 0, 88, 0, 0, 0, 0, 0, 0, + 0, 0, 247, 82, 108, 110, 103, 104, 105, 0, + 97, 98, 99, 102, 0, 0, 0, 84, 0, 0, + 0, 85, 0, 87, 86, 106, 107, 111, 109, 113, + 112, 0, 0, 0, 0, 83, 0, 0, 0, 0, + 91, 92, 94, 95, 96, 93, 0, 0, 89, 90, + 100, 101, 0, 0, 0, 0, 0, 0, 0, 88, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, + 108, 110, 103, 104, 105, 0, 97, 98, 99, 102, + 0, 0, 0, 84, 244, 0, 0, 85, 0, 87, + 86, 106, 107, 111, 109, 113, 112, 0, 0, 0, + 0, 83, 0, 0, 0, 0, 91, 92, 94, 95, + 96, 93, 0, 0, 89, 90, 100, 101, 0, 0, + 0, 0, 0, 0, 0, 88, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 82, 108, 110, 103, 104, + 105, 0, 97, 98, 99, 102, 0, 0, 0, 84, + 221, 0, 0, 85, 0, 87, 86, 106, 107, 111, + 109, 113, 112, 0, 0, 0, 0, 83, 0, 0, + 0, 0, 91, 92, 94, 95, 96, 93, 0, 0, + 89, 90, 100, 101, 0, 0, 0, 0, 0, 0, + 0, 88, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 82, 108, 110, 103, 104, 105, 0, 97, 98, + 99, 102, 0, 208, 0, 84, 0, 0, 0, 85, + 0, 87, 86, 106, 107, 111, 109, 113, 112, 0, + 0, 0, 0, 83, 0, 0, 0, 0, 91, 92, + 94, 95, 96, 93, 0, 0, 89, 90, 100, 101, + 0, 0, 0, 0, 0, 0, 0, 88, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 82, 108, 110, + 103, 104, 105, 0, 97, 98, 99, 102, 0, 199, + 0, 84, 0, 0, 0, 85, 0, 87, 86, 106, + 107, 111, 109, 113, 112, 0, 0, 0, 0, 83, + 0, 0, 0, 0, 91, 92, 94, 95, 96, 93, + 0, 0, 89, 90, 100, 101, 0, 0, 0, 0, + 0, 0, 0, 88, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 82, 108, 110, 103, 104, 105, 0, + 97, 98, 99, 102, 0, 0, 0, 84, 0, 0, + 0, 85, 0, 87, 86, 106, 107, 111, 109, 113, + 112, 0, 0, 0, 0, 83, 0, 0, 0, 0, + 91, 92, 94, 95, 96, 93, 0, 0, 89, 90, + 100, 101, 0, 0, 0, 0, 0, 0, 0, 88, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, + 108, 110, 103, 104, 105, 0, 97, 98, 99, 102, + 0, 0, 0, 161, 0, 0, 0, 85, 0, 87, + 86, 106, 107, 111, 109, 113, 112, 0, 0, 0, + 0, 83, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 89, 90, 100, 101, 0, 0, + 0, 0, 0, 0, 0, 88, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 82, 108, 110, 103, 104, + 105, 0, 97, 98, 99, 102, 0, 0, 0, 84, + 0, 0, 0, 85, 0, 87, 86, 106, 107, 111, + 109, 113, 112, 0, 0, 0, 0, 83, 0, 0, + 35, 52, 53, 0, 0, 31, 0, 0, 0, 0, + 89, 90, 100, 101, 0, 0, 0, 0, 0, 39, + 54, 55, 56, 0, 0, 0, 0, 0, 0, 0, + 0, 82, 108, 110, 103, 104, 105, 0, 97, 98, + 99, 102, 0, 0, 0, 84, 40, 57, 0, 85, + 37, 87, 0, 41, 38, 0, 0, 0, 0, 0, + 0, 51, 0, 59, 61, 0, 0, 60, 0, 42, + 0, 34, 35, 52, 53, 32, 336, 31, 58, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 39, 54, 55, 56, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 35, 52, + 53, 0, 0, 31, 0, 0, 0, 0, 40, 57, + 0, 0, 37, 0, 0, 41, 38, 39, 54, 55, + 56, 0, 0, 51, 0, 59, 61, 0, 0, 60, + 0, 42, 0, 34, 35, 52, 53, 32, 305, 31, + 58, 0, 0, 0, 40, 57, 0, 0, 37, 0, + 0, 41, 38, 39, 54, 55, 56, 0, 0, 51, + 0, 59, 61, 0, 0, 60, 0, 42, 0, 34, + 0, 0, 261, 32, 0, 0, 58, 0, 0, 0, + 40, 57, 0, 0, 37, 0, 0, 41, 38, 0, + 224, 0, 0, 0, 0, 51, 0, 59, 61, 0, + 0, 60, 0, 42, 0, 34, 35, 52, 53, 32, + 0, 31, 58, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 39, 54, 55, 56, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 35, 52, 53, 0, 0, 31, 0, 0, + 0, 0, 40, 57, 0, 0, 37, 0, 0, 41, + 38, 39, 54, 55, 56, 0, 0, 51, 0, 59, + 61, 0, 0, 60, 0, 42, 0, 34, 0, 0, + 205, 32, 0, 0, 58, 0, 0, 0, 40, 57, + 0, 0, 37, 0, 0, 41, 38, 0, 172, 0, + 0, 0, 0, 51, 0, 59, 61, 0, 0, 60, + 0, 42, 0, 34, 35, 52, 53, 32, 0, 31, + 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 39, 54, 55, 56, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 35, 52, 53, 0, 0, 31, 0, 0, 0, 0, + 40, 57, 0, 0, 37, 0, 0, 41, 38, 39, + 54, 55, 56, 0, 0, 51, 0, 59, 61, 0, + 0, 60, 0, 42, 0, 34, 35, 52, 53, 32, + 0, 31, 58, 0, 0, 0, 40, 57, 0, 0, + 37, 0, 0, 41, 38, 39, 54, 55, 56, 0, + 0, 51, 0, 59, 61, 0, 0, 60, 0, 362, + 0, 34, 35, 52, 53, 32, 0, 31, 58, 0, + 0, 0, 40, 57, 0, 0, 37, 0, 0, 41, + 38, 39, 54, 55, 56, 0, 0, 51, 0, 59, + 61, 0, 0, 60, 0, 316, 0, 34, 35, 52, + 53, 32, 0, 31, 58, 0, 0, 0, 40, 57, + 0, 0, 37, 0, 0, 41, 38, 39, 54, 55, + 56, 0, 0, 51, 0, 59, 61, 0, 0, 60, + 0, 314, 0, 34, 0, 0, 0, 32, 0, 0, + 58, 0, 0, 0, 40, 57, 0, 0, 37, 0, + 0, 41, 38, 0, 86, 106, 107, 111, 109, 51, + 112, 59, 61, 0, 0, 60, 0, 258, 0, 34, + 0, 0, 0, 32, 0, 0, 58, 0, 89, 90, + 100, 101, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 108, 110, 103, 104, 105, 0, 97, 98, 99, 102, + 35, 150, 53, 84, 0, 31, 0, 85, 0, 87, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, + 54, 55, 56, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 75, 52, 53, 0, + 0, 31, 0, 0, 0, 0, 40, 57, 0, 0, + 37, 0, 0, 41, 38, 39, 54, 55, 56, 0, + 0, 51, 0, 59, 61, 0, 0, 60, 0, 42, + 0, 34, 0, 0, 0, 32, 0, 0, 58, 0, + 0, 0, 40, 57, 0, 0, 37, 0, 0, 41, + 38, 86, 106, 107, 111, 109, 0, 51, 0, 59, + 61, 0, 0, 60, 0, 42, 0, 34, 0, 0, + 0, 32, 0, 0, 58, 89, 90, 100, 101, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 108, 110, 103, + 104, 105, 0, 97, 98, 99, 102, 0, 0, 0, + 84, 0, 0, 0, 85, 0, 87, +} +var yyPact = [...]int{ + + -67, -1000, 631, -67, -1000, -71, -71, -1000, -1000, -1000, + -1000, -1000, -1000, 3490, 3490, 306, 213, 3782, 93, 89, + 283, -1000, -1000, 1226, -1000, -1000, 3490, 465, 3490, -1000, + -1000, 146, -59, 206, 3490, 40, -28, 76, 62, 59, + 53, -7, -71, -1000, -1000, -1000, -1000, -1000, 302, 71, + -1000, 3746, -1000, -1000, -1000, -1000, -1000, 3490, 3490, 3490, + 3490, 3490, -1000, -1000, -1000, -1000, -1000, 631, -71, -1000, + -8, 2942, 2942, 210, -67, 50, 3008, 3490, 3490, 200, + 3490, 3490, 3490, 3490, 3490, 3418, 3490, 303, 3490, -1000, + -1000, 3490, 3490, 3490, 3490, 3490, 3490, 3490, 3490, 3490, + 3490, 3490, 3490, 3490, 3490, 3490, 3490, 3490, 3490, 3490, + 3490, 3490, 3490, 3490, 2876, -67, 49, 553, 3382, -34, + 40, 2810, 302, 16, -25, 3490, -71, -10, -1000, 206, + 206, -15, 206, 209, -30, 2744, 3490, 3310, 3490, 3490, + 206, 179, -71, 206, 3490, 65, -1000, 3490, 3490, -71, + -1000, -41, 3074, -41, -41, -41, -41, -1000, -67, 197, + 3490, 3490, 1160, 2678, 3490, -67, 2942, 2942, 2612, 3140, + 163, 1094, 3490, 189, -1000, 3074, 2942, 2942, 2942, 2942, + 2942, 2942, 189, 189, 189, 189, 189, 189, 120, 120, + 120, 484, 484, 484, 484, 484, 484, 3825, 3678, -67, + 195, -71, 3490, -71, -67, 3634, 2546, 3274, -71, 139, + 302, -1000, -60, -71, 301, -42, -42, 206, -42, -71, + -25, -1000, 121, 1028, 3490, 2480, 2414, -50, -44, 300, + 3490, -35, -64, 2348, 3490, -8, 2942, 3490, 193, 266, + 110, 77, -1000, 3490, -1000, 2282, 192, 3490, 12, -1000, + -1000, 3238, 962, 191, -1000, 2216, 297, 186, -67, 2150, + 3598, 3562, 2084, 247, 208, 4, 45, -71, -31, -71, + 3490, -1000, -38, 295, 1, -1000, -1000, 3166, 896, -1000, + -1000, -1000, -1000, 3490, -3, -64, 206, 185, -71, 3490, + -8, 2942, -28, -1000, 180, 0, -1000, -1, -1000, 2018, + -67, -1000, 3074, -1000, 830, -1000, -1000, 3490, -1000, -67, + -1000, -1000, 183, -67, -67, 1952, -67, 1886, 3526, -36, + -1000, -1000, 134, 3490, -67, 207, 204, -9, -71, -1000, + -60, 206, -63, 206, -1000, 764, -1000, -1000, 3490, 698, + 3490, 182, -51, -1000, 3490, 2942, 203, -67, -1000, -1000, + -1000, 177, -1000, 3490, 1820, 175, -1000, 168, 159, -67, + 152, -67, -67, 1754, 150, -1000, -1000, -67, 1688, 13, + 140, -67, -67, 202, 137, -42, 135, -71, -42, -1000, + 3490, 1622, -1000, 3490, 1556, -1000, -71, 1490, -67, 128, + -1000, 1424, -1000, -1000, -1000, -1000, 122, -1000, 107, 106, + -67, -1000, -1000, -67, -67, -1000, 104, 103, -67, -1000, + -1000, 294, 1358, -1000, 1292, -1000, 3490, 3490, 102, 263, + -1000, -1000, -1000, -1000, 100, -1000, -1000, -1000, -1000, 98, + 206, -1000, -1000, -64, 2942, 257, 199, -1000, -1000, -42, + 96, 164, -67, -1000, -67, 78, 46, -1000, -1000, +} +var yyPgo = [...]int{ + + 0, 41, 353, 279, 303, 352, 350, 349, 348, 346, + 344, 6, 5, 56, 0, 8, 25, 343, 2, 340, + 338, 7, 337, 4, 335, 333, 330, 328, 323, 321, + 320, 316, 314, 313, 311, 159, 1, 179, 48, +} +var yyR1 = [...]int{ + + 0, 1, 1, 2, 2, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 4, 4, 5, + 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 9, 10, + 10, 10, 10, 10, 11, 11, 12, 13, 13, 13, + 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 15, 15, 15, 16, 16, + 16, 16, 16, 16, 16, 17, 17, 18, 18, 19, + 19, 20, 21, 22, 22, 22, 22, 22, 22, 23, + 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 25, 25, 26, 26, 26, 26, 26, 27, + 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, + 28, 32, 32, 32, 32, 32, 32, 31, 31, 31, + 30, 30, 30, 30, 30, 30, 29, 29, 33, 33, + 34, 34, 34, 35, 35, 37, 37, 38, 36, 36, + 36, 36, +} +var yyR2 = [...]int{ + + 0, 1, 2, 2, 3, 0, 1, 1, 1, 2, + 2, 5, 13, 12, 9, 8, 6, 5, 6, 5, + 4, 6, 4, 1, 1, 1, 1, 1, 1, 4, + 3, 3, 3, 3, 5, 7, 5, 4, 7, 5, + 6, 7, 7, 8, 7, 8, 8, 9, 7, 0, + 1, 1, 2, 2, 4, 4, 3, 0, 1, 4, + 4, 1, 1, 5, 3, 7, 8, 8, 9, 2, + 5, 7, 3, 5, 4, 5, 4, 4, 4, 4, + 4, 4, 4, 6, 8, 7, 3, 6, 10, 5, + 1, 1, 1, 1, 1, 0, 1, 4, 1, 3, + 2, 2, 5, 2, 6, 2, 5, 2, 3, 1, + 1, 3, 1, 2, 1, 1, 1, 1, 1, 0, + 3, 6, 6, 5, 5, 7, 8, 6, 5, 5, + 7, 8, 3, 2, 2, 2, 2, 2, 2, 1, + 1, 1, 1, 2, 2, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 0, 1, + 2, 1, 1, 0, 1, 1, 2, 1, 0, 2, + 1, 1, +} +var yyChk = [...]int{ + + -1000, -1, -33, -2, -34, 78, -37, -38, 83, -3, + -4, 38, 39, 10, 12, 28, 29, 47, 55, 56, + -7, -8, -9, -14, -5, -6, 13, 15, 44, -19, + -22, 9, 79, -18, 75, 4, -21, 54, 58, 23, + 50, 57, 73, -24, -25, -26, -27, -28, 11, -13, + -20, 65, 5, 6, 24, 25, 26, 51, 82, 67, + 71, 68, -32, -31, -30, -29, -33, -34, -37, -38, + -13, -14, -14, 4, 73, 4, -14, 75, 75, 14, + 59, 52, 61, 27, 75, 79, 16, 81, 51, 40, + 41, 32, 33, 37, 34, 35, 36, 68, 69, 70, + 42, 43, 71, 64, 65, 66, 17, 18, 62, 20, + 63, 19, 22, 21, -14, 73, -15, -14, 78, -4, + 4, -14, 75, 4, 80, -35, -37, -16, 4, 68, + -18, 57, 48, 49, 79, -14, 75, 79, 75, 75, + 75, 75, 73, 79, -35, -15, 4, 59, 52, 77, + 5, -14, -14, -14, -14, -14, -14, -3, 73, -1, + 75, 75, -14, -14, 13, 73, -14, -14, -14, -14, + -13, -14, 60, -14, 4, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, 73, + -1, -37, 16, 77, 73, 78, -14, 78, 73, -15, + 75, -18, -13, 73, 81, -16, -16, 79, -16, 73, + 80, 76, -13, -14, 60, -14, -14, -16, -16, 53, + -35, -16, -23, -14, 59, -13, -14, -35, -1, 74, + -13, -13, 76, 77, 76, -14, -1, 60, 8, 76, + 80, 60, -14, -1, 74, -14, -35, -1, 73, -14, + 78, 78, -14, -35, 76, 8, -15, 77, -36, -37, + -35, 4, -16, -35, 8, 76, 80, 60, -14, 76, + 76, 76, 76, 77, 4, -23, 80, -36, 77, 60, + -13, -14, -21, 74, 30, 8, 76, 8, 76, -14, + 73, 74, -14, 76, -14, 80, 80, 60, 74, 73, + 4, 74, -1, 73, 73, -14, 73, -14, 78, -10, + -12, -11, 46, 45, 73, 76, 76, 8, -37, 80, + -13, 80, -17, 4, 76, -14, 80, 80, 60, -14, + 77, -36, -16, 74, -35, -14, 4, 73, 76, 76, + 76, -1, 80, 60, -14, -1, 74, -1, -1, 73, + -1, 73, 73, -14, -35, -11, -12, 60, -14, -13, + -1, 73, 73, 76, -36, -16, -35, 77, -16, 80, + 60, -14, 76, 77, -14, 74, 73, -14, 73, -1, + 74, -14, 80, 74, 74, 74, -1, 74, -1, -1, + 73, 74, -1, 60, 60, 74, -1, -1, 73, 74, + 74, -35, -14, 80, -14, 76, -35, 60, -1, 74, + 80, 74, 74, 74, -1, -1, -1, 74, 74, -1, + 4, 80, 76, -23, -14, 74, 31, 74, 74, -16, + -36, 31, 73, 74, 73, -1, -1, 74, 74, +} +var yyDef = [...]int{ + + 168, -2, -2, 168, 169, 172, 171, 175, 177, 3, + 6, 7, 8, 57, 0, 0, 0, 0, 0, 0, + 23, 24, 25, -2, 27, 28, 0, -2, 0, 61, + 62, 0, 173, 0, 0, 112, 110, 0, 0, 0, + 0, 0, 173, 90, 91, 92, 93, 94, 95, 0, + 109, 0, 114, 115, 116, 117, 118, 0, 0, 0, + 0, 0, 139, 140, 141, 142, 2, -2, 170, 176, + 9, 58, 10, 0, 168, 112, 0, 0, 0, 0, + 0, 0, 0, 0, 57, 0, 0, 0, 0, 143, + 144, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 168, 0, 58, 0, 0, + -2, 0, 95, 0, -2, 57, 174, 0, 98, 0, + 0, 0, 0, 0, 0, 0, 57, 0, 0, 0, + 0, 0, 173, 0, 119, 0, 96, 57, 0, 173, + 113, 134, 133, 135, 136, 137, 138, 4, 168, 0, + 57, 57, 0, 0, 0, 168, 30, 32, 0, 64, + 0, 0, 0, 86, 111, 132, 145, 146, 147, 148, + 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, + 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, + 0, 171, 0, 173, 168, 0, 0, 0, 173, 0, + 95, 108, 178, 173, 0, 100, 101, 0, 103, 173, + 107, 72, 0, 0, 0, 0, 0, 0, 0, 0, + 119, 0, 178, 0, 57, 31, 33, 0, 0, 0, + 0, 0, 20, 0, 22, 0, 0, 0, 0, 76, + 78, 0, 0, 0, 37, 0, 0, 0, 168, 0, + 0, 0, 0, 49, 0, 0, 0, -2, 0, 180, + 57, 99, 0, 0, 0, 74, 77, 0, 0, 79, + 80, 81, 82, 0, 0, 178, 0, 0, -2, 0, + 29, 59, -2, 11, 0, 0, -2, 0, -2, 0, + 168, 36, 63, 75, 0, 128, 129, 0, 34, 168, + 97, 39, 0, 168, 168, 0, 168, 0, 0, 173, + 50, 51, 0, 57, 168, 0, 0, 0, -2, 70, + 178, 0, 173, 0, 73, 0, 123, 124, 0, 0, + 0, 0, 0, 89, 0, 120, 0, 168, -2, -2, + 21, 0, 127, 0, 0, 0, 40, 0, 0, 168, + 0, 168, 168, 0, 0, 52, 53, 168, 58, 0, + 0, 168, 168, 0, 0, 102, 0, 173, 105, 122, + 0, 0, 83, 0, 0, 87, 173, 0, 168, 0, + 35, 0, 130, 38, 41, 42, 0, 44, 0, 0, + 168, 48, 56, 168, 168, 65, 0, 0, 168, 71, + 104, 0, 0, 125, 0, 85, 119, 0, 0, 15, + 131, 43, 45, 46, 0, 54, 55, 66, 67, 0, + 0, 126, 84, 178, 121, 14, 0, 47, 68, 106, + 0, 0, 168, 88, 168, 0, 0, 13, 12, +} +var yyTok1 = [...]int{ + + 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 83, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 82, 3, 3, 3, 70, 71, 3, + 75, 76, 68, 64, 77, 65, 81, 69, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 60, 78, + 62, 59, 63, 61, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 79, 3, 80, 67, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 73, 66, 74, +} +var yyTok2 = [...]int{ + + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 72, +} +var yyTok3 = [...]int{ + 0, +} + +var yyErrorMessages = [...]struct { + state int + token int + msg string +}{} + +//line yaccpar:1 + +/* parser for yacc output */ + +var ( + yyDebug = 0 + yyErrorVerbose = false +) + +type yyLexer interface { + Lex(lval *yySymType) int + Error(s string) +} + +type yyParser interface { + Parse(yyLexer) int + Lookahead() int +} + +type yyParserImpl struct { + lval yySymType + stack [yyInitialStackSize]yySymType + char int +} + +func (p *yyParserImpl) Lookahead() int { + return p.char +} + +func yyNewParser() yyParser { + return &yyParserImpl{} +} + +const yyFlag = -1000 + +func yyTokname(c int) string { + if c >= 1 && c-1 < len(yyToknames) { + if yyToknames[c-1] != "" { + return yyToknames[c-1] + } + } + return __yyfmt__.Sprintf("tok-%v", c) +} + +func yyStatname(s int) string { + if s >= 0 && s < len(yyStatenames) { + if yyStatenames[s] != "" { + return yyStatenames[s] + } + } + return __yyfmt__.Sprintf("state-%v", s) +} + +func yyErrorMessage(state, lookAhead int) string { + const TOKSTART = 4 + + if !yyErrorVerbose { + return "syntax error" + } + + for _, e := range yyErrorMessages { + if e.state == state && e.token == lookAhead { + return "syntax error: " + e.msg + } + } + + res := "syntax error: unexpected " + yyTokname(lookAhead) + + // To match Bison, suggest at most four expected tokens. + expected := make([]int, 0, 4) + + // Look for shiftable tokens. + base := yyPact[state] + for tok := TOKSTART; tok-1 < len(yyToknames); tok++ { + if n := base + tok; n >= 0 && n < yyLast && yyChk[yyAct[n]] == tok { + if len(expected) == cap(expected) { + return res + } + expected = append(expected, tok) + } + } + + if yyDef[state] == -2 { + i := 0 + for yyExca[i] != -1 || yyExca[i+1] != state { + i += 2 + } + + // Look for tokens that we accept or reduce. + for i += 2; yyExca[i] >= 0; i += 2 { + tok := yyExca[i] + if tok < TOKSTART || yyExca[i+1] == 0 { + continue + } + if len(expected) == cap(expected) { + return res + } + expected = append(expected, tok) + } + + // If the default action is to accept or reduce, give up. + if yyExca[i+1] != 0 { + return res + } + } + + for i, tok := range expected { + if i == 0 { + res += ", expecting " + } else { + res += " or " + } + res += yyTokname(tok) + } + return res +} + +func yylex1(lex yyLexer, lval *yySymType) (char, token int) { + token = 0 + char = lex.Lex(lval) + if char <= 0 { + token = yyTok1[0] + goto out + } + if char < len(yyTok1) { + token = yyTok1[char] + goto out + } + if char >= yyPrivate { + if char < yyPrivate+len(yyTok2) { + token = yyTok2[char-yyPrivate] + goto out + } + } + for i := 0; i < len(yyTok3); i += 2 { + token = yyTok3[i+0] + if token == char { + token = yyTok3[i+1] + goto out + } + } + +out: + if token == 0 { + token = yyTok2[1] /* unknown char */ + } + if yyDebug >= 3 { + __yyfmt__.Printf("lex %s(%d)\n", yyTokname(token), uint(char)) + } + return char, token +} + +func yyParse(yylex yyLexer) int { + return yyNewParser().Parse(yylex) +} + +func (yyrcvr *yyParserImpl) Parse(yylex yyLexer) int { + var yyn int + var yyVAL yySymType + var yyDollar []yySymType + _ = yyDollar // silence set and not used + yyS := yyrcvr.stack[:] + + Nerrs := 0 /* number of errors */ + Errflag := 0 /* error recovery flag */ + yystate := 0 + yyrcvr.char = -1 + yytoken := -1 // yyrcvr.char translated into internal numbering + defer func() { + // Make sure we report no lookahead when not parsing. + yystate = -1 + yyrcvr.char = -1 + yytoken = -1 + }() + yyp := -1 + goto yystack + +ret0: + return 0 + +ret1: + return 1 + +yystack: + /* put a state and value onto the stack */ + if yyDebug >= 4 { + __yyfmt__.Printf("char %v in %v\n", yyTokname(yytoken), yyStatname(yystate)) + } + + yyp++ + if yyp >= len(yyS) { + nyys := make([]yySymType, len(yyS)*2) + copy(nyys, yyS) + yyS = nyys + } + yyS[yyp] = yyVAL + yyS[yyp].yys = yystate + +yynewstate: + yyn = yyPact[yystate] + if yyn <= yyFlag { + goto yydefault /* simple state */ + } + if yyrcvr.char < 0 { + yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval) + } + yyn += yytoken + if yyn < 0 || yyn >= yyLast { + goto yydefault + } + yyn = yyAct[yyn] + if yyChk[yyn] == yytoken { /* valid shift */ + yyrcvr.char = -1 + yytoken = -1 + yyVAL = yyrcvr.lval + yystate = yyn + if Errflag > 0 { + Errflag-- + } + goto yystack + } + +yydefault: + /* default state action */ + yyn = yyDef[yystate] + if yyn == -2 { + if yyrcvr.char < 0 { + yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval) + } + + /* look through exception table */ + xi := 0 + for { + if yyExca[xi+0] == -1 && yyExca[xi+1] == yystate { + break + } + xi += 2 + } + for xi += 2; ; xi += 2 { + yyn = yyExca[xi+0] + if yyn < 0 || yyn == yytoken { + break + } + } + yyn = yyExca[xi+1] + if yyn < 0 { + goto ret0 + } + } + if yyn == 0 { + /* error ... attempt to resume parsing */ + switch Errflag { + case 0: /* brand new error */ + yylex.Error(yyErrorMessage(yystate, yytoken)) + Nerrs++ + if yyDebug >= 1 { + __yyfmt__.Printf("%s", yyStatname(yystate)) + __yyfmt__.Printf(" saw %s\n", yyTokname(yytoken)) + } + fallthrough + + case 1, 2: /* incompletely recovered error ... try again */ + Errflag = 3 + + /* find a state where "error" is a legal shift action */ + for yyp >= 0 { + yyn = yyPact[yyS[yyp].yys] + yyErrCode + if yyn >= 0 && yyn < yyLast { + yystate = yyAct[yyn] /* simulate a shift of "error" */ + if yyChk[yystate] == yyErrCode { + goto yystack + } + } + + /* the current p has no shift on "error", pop stack */ + if yyDebug >= 2 { + __yyfmt__.Printf("error recovery pops state %d\n", yyS[yyp].yys) + } + yyp-- + } + /* there is no state on the stack with an error shift ... abort */ + goto ret1 + + case 3: /* no shift yet; clobber input char */ + if yyDebug >= 2 { + __yyfmt__.Printf("error recovery discards %s\n", yyTokname(yytoken)) + } + if yytoken == yyEofCode { + goto ret1 + } + yyrcvr.char = -1 + yytoken = -1 + goto yynewstate /* try again in the same state */ + } + } + + /* reduction by production yyn */ + if yyDebug >= 2 { + __yyfmt__.Printf("reduce %v in:\n\t%v\n", yyn, yyStatname(yystate)) + } + + yynt := yyn + yypt := yyp + _ = yypt // guard against "declared and not used" + + yyp -= yyR2[yyn] + // yyp is now the index of $0. Perform the default action. Iff the + // reduced production is ε, $1 is possibly out of range. + if yyp+1 >= len(yyS) { + nyys := make([]yySymType, len(yyS)*2) + copy(nyys, yyS) + yyS = nyys + } + yyVAL = yyS[yyp+1] + + /* consult goto table to find next state */ + yyn = yyR1[yyn] + yyg := yyPgo[yyn] + yyj := yyg + yyS[yyp].yys + 1 + + if yyj >= yyLast { + yystate = yyAct[yyg] + } else { + yystate = yyAct[yyj] + if yyChk[yystate] != -yyn { + yystate = yyAct[yyg] + } + } + // dummy call; replaced with literal code + switch yynt { + + case 1: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:108 + { + yyVAL.compstmt = nil + } + case 2: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:112 + { + yyVAL.compstmt = yyDollar[1].stmts + } + case 3: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:118 + { + if yyDollar[2].stmt != nil { + yyVAL.stmts = &ast.StmtsStmt{Stmts: []ast.Stmt{yyDollar[2].stmt}} + } + if l, ok := yylex.(*Lexer); ok { + l.stmt = yyVAL.stmts + } + } + case 4: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:127 + { + if yyDollar[3].stmt != nil { + if yyDollar[1].stmts == nil { + yyVAL.stmts = &ast.StmtsStmt{Stmts: []ast.Stmt{yyDollar[3].stmt}} + } else { + stmts := yyDollar[1].stmts.(*ast.StmtsStmt) + stmts.Stmts = append(stmts.Stmts, yyDollar[3].stmt) + } + if l, ok := yylex.(*Lexer); ok { + l.stmt = yyVAL.stmts + } + } + } + case 5: + yyDollar = yyS[yypt-0 : yypt+1] +//line parser.go.y:143 + { + yyVAL.stmt = nil + } + case 6: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:147 + { + yyVAL.stmt = yyDollar[1].stmt_var_or_lets + } + case 7: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:151 + { + yyVAL.stmt = &ast.BreakStmt{} + yyVAL.stmt.SetPosition(yyDollar[1].tok.Position()) + } + case 8: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:156 + { + yyVAL.stmt = &ast.ContinueStmt{} + yyVAL.stmt.SetPosition(yyDollar[1].tok.Position()) + } + case 9: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:161 + { + yyVAL.stmt = &ast.ReturnStmt{Exprs: yyDollar[2].exprs} + yyVAL.stmt.SetPosition(yyDollar[1].tok.Position()) + } + case 10: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:166 + { + yyVAL.stmt = &ast.ThrowStmt{Expr: yyDollar[2].expr} + yyVAL.stmt.SetPosition(yyDollar[1].tok.Position()) + } + case 11: + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:171 + { + yyVAL.stmt = &ast.ModuleStmt{Name: yyDollar[2].tok.Lit, Stmt: yyDollar[4].compstmt} + yyVAL.stmt.SetPosition(yyDollar[1].tok.Position()) + } + case 12: + yyDollar = yyS[yypt-13 : yypt+1] +//line parser.go.y:176 + { + yyVAL.stmt = &ast.TryStmt{Try: yyDollar[3].compstmt, Var: yyDollar[6].tok.Lit, Catch: yyDollar[8].compstmt, Finally: yyDollar[12].compstmt} + yyVAL.stmt.SetPosition(yyDollar[1].tok.Position()) + } + case 13: + yyDollar = yyS[yypt-12 : yypt+1] +//line parser.go.y:181 + { + yyVAL.stmt = &ast.TryStmt{Try: yyDollar[3].compstmt, Catch: yyDollar[7].compstmt, Finally: yyDollar[11].compstmt} + yyVAL.stmt.SetPosition(yyDollar[1].tok.Position()) + } + case 14: + yyDollar = yyS[yypt-9 : yypt+1] +//line parser.go.y:186 + { + yyVAL.stmt = &ast.TryStmt{Try: yyDollar[3].compstmt, Var: yyDollar[6].tok.Lit, Catch: yyDollar[8].compstmt} + yyVAL.stmt.SetPosition(yyDollar[1].tok.Position()) + } + case 15: + yyDollar = yyS[yypt-8 : yypt+1] +//line parser.go.y:191 + { + yyVAL.stmt = &ast.TryStmt{Try: yyDollar[3].compstmt, Catch: yyDollar[7].compstmt} + yyVAL.stmt.SetPosition(yyDollar[1].tok.Position()) + } + case 16: + yyDollar = yyS[yypt-6 : yypt+1] +//line parser.go.y:196 + { + yyVAL.stmt = &ast.GoroutineStmt{Expr: &ast.CallExpr{Name: yyDollar[2].tok.Lit, SubExprs: yyDollar[4].exprs, VarArg: true, Go: true}} + yyVAL.stmt.SetPosition(yyDollar[2].tok.Position()) + } + case 17: + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:201 + { + yyVAL.stmt = &ast.GoroutineStmt{Expr: &ast.CallExpr{Name: yyDollar[2].tok.Lit, SubExprs: yyDollar[4].exprs, Go: true}} + yyVAL.stmt.SetPosition(yyDollar[2].tok.Position()) + } + case 18: + yyDollar = yyS[yypt-6 : yypt+1] +//line parser.go.y:206 + { + yyVAL.stmt = &ast.GoroutineStmt{Expr: &ast.AnonCallExpr{Expr: yyDollar[2].expr, SubExprs: yyDollar[4].exprs, VarArg: true, Go: true}} + yyVAL.stmt.SetPosition(yyDollar[2].expr.Position()) + } + case 19: + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:211 + { + yyVAL.stmt = &ast.GoroutineStmt{Expr: &ast.AnonCallExpr{Expr: yyDollar[2].expr, SubExprs: yyDollar[4].exprs, Go: true}} + yyVAL.stmt.SetPosition(yyDollar[1].tok.Position()) + } + case 20: + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:216 + { + yyVAL.stmt = &ast.DeleteStmt{Item: yyDollar[3].expr} + yyVAL.stmt.SetPosition(yyDollar[1].tok.Position()) + } + case 21: + yyDollar = yyS[yypt-6 : yypt+1] +//line parser.go.y:221 + { + yyVAL.stmt = &ast.DeleteStmt{Item: yyDollar[3].expr, Key: yyDollar[5].expr} + yyVAL.stmt.SetPosition(yyDollar[1].tok.Position()) + } + case 22: + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:226 + { + yyVAL.stmt = &ast.CloseStmt{Expr: yyDollar[3].expr} + yyVAL.stmt.SetPosition(yyDollar[1].tok.Position()) + } + case 23: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:231 + { + yyVAL.stmt = yyDollar[1].stmt_if + } + case 24: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:235 + { + yyVAL.stmt = yyDollar[1].stmt_for + } + case 25: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:239 + { + yyVAL.stmt = yyDollar[1].stmt_switch + } + case 26: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:243 + { + yyVAL.stmt = &ast.ExprStmt{Expr: yyDollar[1].expr} + yyVAL.stmt.SetPosition(yyDollar[1].expr.Position()) + } + case 27: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:250 + { + yyVAL.stmt_var_or_lets = yyDollar[1].stmt_var + } + case 28: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:254 + { + yyVAL.stmt_var_or_lets = yyDollar[1].stmt_lets + } + case 29: + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:260 + { + yyVAL.stmt_var = &ast.VarStmt{Names: yyDollar[2].expr_idents, Exprs: yyDollar[4].exprs} + yyVAL.stmt_var.SetPosition(yyDollar[1].tok.Position()) + } + case 30: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:267 + { + yyVAL.stmt_lets = &ast.LetsStmt{LHSS: []ast.Expr{yyDollar[1].expr}, RHSS: []ast.Expr{yyDollar[3].expr}} + yyVAL.stmt_lets.SetPosition(yyDollar[1].expr.Position()) + } + case 31: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:272 + { + if len(yyDollar[1].exprs) == 2 && len(yyDollar[3].exprs) == 1 { + if _, ok := yyDollar[3].exprs[0].(*ast.ItemExpr); ok { + yyVAL.stmt_lets = &ast.LetMapItemStmt{LHSS: yyDollar[1].exprs, RHS: yyDollar[3].exprs[0]} + } else { + yyVAL.stmt_lets = &ast.LetsStmt{LHSS: yyDollar[1].exprs, RHSS: yyDollar[3].exprs} + } + } else { + yyVAL.stmt_lets = &ast.LetsStmt{LHSS: yyDollar[1].exprs, RHSS: yyDollar[3].exprs} + } + } + case 32: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:284 + { + yyVAL.stmt_lets = &ast.ChanStmt{LHS: yyDollar[1].expr, RHS: yyDollar[3].expr} + yyVAL.stmt_lets.SetPosition(yyDollar[1].expr.Position()) + } + case 33: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:289 + { + if len(yyDollar[1].exprs) == 2 { + chanStmt := &ast.ChanStmt{LHS: yyDollar[1].exprs[0].(ast.Expr), OkExpr: yyDollar[1].exprs[1].(ast.Expr), RHS: yyDollar[3].expr} + yyVAL.stmt_lets = chanStmt + yyVAL.stmt_lets.SetPosition(chanStmt.LHS.Position()) + } else if len(yyDollar[1].exprs) < 2 { + yylex.Error("missing expressions on left side of channel operator") + yyVAL.stmt_lets = &ast.ChanStmt{RHS: yyDollar[3].expr} + yyVAL.stmt_lets.SetPosition(yyDollar[2].tok.Position()) + } + } + case 34: + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:303 + { + yyVAL.stmt_if = &ast.IfStmt{If: yyDollar[2].expr, Then: yyDollar[4].compstmt, Else: nil} + yyVAL.stmt_if.SetPosition(yyDollar[1].tok.Position()) + } + case 35: + yyDollar = yyS[yypt-7 : yypt+1] +//line parser.go.y:308 + { + ifStmt := yyDollar[1].stmt_if.(*ast.IfStmt) + ifStmt.ElseIf = append(ifStmt.ElseIf, &ast.IfStmt{If: yyDollar[4].expr, Then: yyDollar[6].compstmt}) + } + case 36: + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:313 + { + ifStmt := yyDollar[1].stmt_if.(*ast.IfStmt) + if ifStmt.Else != nil { + yylex.Error("multiple else statement") + } + ifStmt.Else = yyDollar[4].compstmt + } + case 37: + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:323 + { + yyVAL.stmt_for = &ast.LoopStmt{Stmt: yyDollar[3].compstmt} + yyVAL.stmt_for.SetPosition(yyDollar[1].tok.Position()) + } + case 38: + yyDollar = yyS[yypt-7 : yypt+1] +//line parser.go.y:328 + { + if len(yyDollar[2].expr_idents) < 1 { + yylex.Error("missing identifier") + } else if len(yyDollar[2].expr_idents) > 2 { + yylex.Error("too many identifiers") + } else { + yyVAL.stmt_for = &ast.ForStmt{Vars: yyDollar[2].expr_idents, Value: yyDollar[4].expr, Stmt: yyDollar[6].compstmt} + yyVAL.stmt_for.SetPosition(yyDollar[1].tok.Position()) + } + } + case 39: + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:339 + { + yyVAL.stmt_for = &ast.LoopStmt{Expr: yyDollar[2].expr, Stmt: yyDollar[4].compstmt} + yyVAL.stmt_for.SetPosition(yyDollar[1].tok.Position()) + } + case 40: + yyDollar = yyS[yypt-6 : yypt+1] +//line parser.go.y:344 + { + yyVAL.stmt_for = &ast.CForStmt{Stmt: yyDollar[5].compstmt} + yyVAL.stmt_for.SetPosition(yyDollar[1].tok.Position()) + } + case 41: + yyDollar = yyS[yypt-7 : yypt+1] +//line parser.go.y:349 + { + yyVAL.stmt_for = &ast.CForStmt{Expr3: yyDollar[4].expr, Stmt: yyDollar[6].compstmt} + yyVAL.stmt_for.SetPosition(yyDollar[1].tok.Position()) + } + case 42: + yyDollar = yyS[yypt-7 : yypt+1] +//line parser.go.y:354 + { + yyVAL.stmt_for = &ast.CForStmt{Expr2: yyDollar[3].expr, Stmt: yyDollar[6].compstmt} + yyVAL.stmt_for.SetPosition(yyDollar[1].tok.Position()) + } + case 43: + yyDollar = yyS[yypt-8 : yypt+1] +//line parser.go.y:359 + { + yyVAL.stmt_for = &ast.CForStmt{Expr2: yyDollar[3].expr, Expr3: yyDollar[5].expr, Stmt: yyDollar[7].compstmt} + yyVAL.stmt_for.SetPosition(yyDollar[1].tok.Position()) + } + case 44: + yyDollar = yyS[yypt-7 : yypt+1] +//line parser.go.y:364 + { + yyVAL.stmt_for = &ast.CForStmt{Stmt1: yyDollar[2].stmt_var_or_lets, Stmt: yyDollar[6].compstmt} + yyVAL.stmt_for.SetPosition(yyDollar[1].tok.Position()) + } + case 45: + yyDollar = yyS[yypt-8 : yypt+1] +//line parser.go.y:369 + { + yyVAL.stmt_for = &ast.CForStmt{Stmt1: yyDollar[2].stmt_var_or_lets, Expr3: yyDollar[5].expr, Stmt: yyDollar[7].compstmt} + yyVAL.stmt_for.SetPosition(yyDollar[1].tok.Position()) + } + case 46: + yyDollar = yyS[yypt-8 : yypt+1] +//line parser.go.y:374 + { + yyVAL.stmt_for = &ast.CForStmt{Stmt1: yyDollar[2].stmt_var_or_lets, Expr2: yyDollar[4].expr, Stmt: yyDollar[7].compstmt} + yyVAL.stmt_for.SetPosition(yyDollar[1].tok.Position()) + } + case 47: + yyDollar = yyS[yypt-9 : yypt+1] +//line parser.go.y:379 + { + yyVAL.stmt_for = &ast.CForStmt{Stmt1: yyDollar[2].stmt_var_or_lets, Expr2: yyDollar[4].expr, Expr3: yyDollar[6].expr, Stmt: yyDollar[8].compstmt} + yyVAL.stmt_for.SetPosition(yyDollar[1].tok.Position()) + } + case 48: + yyDollar = yyS[yypt-7 : yypt+1] +//line parser.go.y:386 + { + switchStmt := yyDollar[5].stmt_switch_cases.(*ast.SwitchStmt) + switchStmt.Expr = yyDollar[2].expr + yyVAL.stmt_switch = switchStmt + yyVAL.stmt_switch.SetPosition(yyDollar[1].tok.Position()) + } + case 49: + yyDollar = yyS[yypt-0 : yypt+1] +//line parser.go.y:395 + { + yyVAL.stmt_switch_cases = &ast.SwitchStmt{} + } + case 50: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:399 + { + yyVAL.stmt_switch_cases = &ast.SwitchStmt{Default: yyDollar[1].stmt_switch_default} + } + case 51: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:403 + { + yyVAL.stmt_switch_cases = &ast.SwitchStmt{Cases: []ast.Stmt{yyDollar[1].stmt_switch_case}} + } + case 52: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:407 + { + switchStmt := yyDollar[1].stmt_switch_cases.(*ast.SwitchStmt) + switchStmt.Cases = append(switchStmt.Cases, yyDollar[2].stmt_switch_case) + yyVAL.stmt_switch_cases = switchStmt + } + case 53: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:413 + { + switchStmt := yyDollar[1].stmt_switch_cases.(*ast.SwitchStmt) + if switchStmt.Default != nil { + yylex.Error("multiple default statement") + } + switchStmt.Default = yyDollar[2].stmt_switch_default + } + case 54: + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:423 + { + yyVAL.stmt_switch_case = &ast.SwitchCaseStmt{Exprs: []ast.Expr{yyDollar[2].expr}, Stmt: yyDollar[4].compstmt} + yyVAL.stmt_switch_case.SetPosition(yyDollar[1].tok.Position()) + } + case 55: + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:428 + { + yyVAL.stmt_switch_case = &ast.SwitchCaseStmt{Exprs: yyDollar[2].exprs, Stmt: yyDollar[4].compstmt} + yyVAL.stmt_switch_case.SetPosition(yyDollar[1].tok.Position()) + } + case 56: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:435 + { + yyVAL.stmt_switch_default = yyDollar[3].compstmt + } + case 57: + yyDollar = yyS[yypt-0 : yypt+1] +//line parser.go.y:442 + { + yyVAL.exprs = nil + } + case 58: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:446 + { + yyVAL.exprs = []ast.Expr{yyDollar[1].expr} + } + case 59: + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:450 + { + if len(yyDollar[1].exprs) == 0 { + yylex.Error("syntax error: unexpected ','") + } + yyVAL.exprs = append(yyDollar[1].exprs, yyDollar[4].expr) + } + case 60: + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:457 + { + if len(yyDollar[1].exprs) == 0 { + yylex.Error("syntax error: unexpected ','") + } + yyVAL.exprs = append(yyDollar[1].exprs, yyDollar[4].expr_ident) + } + case 61: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:466 + { + yyVAL.expr = yyDollar[1].expr_member_or_ident + } + case 62: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:470 + { + yyVAL.expr = yyDollar[1].expr_literals + } + case 63: + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:474 + { + yyVAL.expr = &ast.TernaryOpExpr{Expr: yyDollar[1].expr, LHS: yyDollar[3].expr, RHS: yyDollar[5].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 64: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:479 + { + yyVAL.expr = &ast.NilCoalescingOpExpr{LHS: yyDollar[1].expr, RHS: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 65: + yyDollar = yyS[yypt-7 : yypt+1] +//line parser.go.y:484 + { + yyVAL.expr = &ast.FuncExpr{Params: yyDollar[3].expr_idents, Stmt: yyDollar[6].compstmt} + yyVAL.expr.SetPosition(yyDollar[1].tok.Position()) + } + case 66: + yyDollar = yyS[yypt-8 : yypt+1] +//line parser.go.y:489 + { + yyVAL.expr = &ast.FuncExpr{Params: yyDollar[3].expr_idents, Stmt: yyDollar[7].compstmt, VarArg: true} + yyVAL.expr.SetPosition(yyDollar[1].tok.Position()) + } + case 67: + yyDollar = yyS[yypt-8 : yypt+1] +//line parser.go.y:494 + { + yyVAL.expr = &ast.FuncExpr{Name: yyDollar[2].tok.Lit, Params: yyDollar[4].expr_idents, Stmt: yyDollar[7].compstmt} + yyVAL.expr.SetPosition(yyDollar[1].tok.Position()) + } + case 68: + yyDollar = yyS[yypt-9 : yypt+1] +//line parser.go.y:499 + { + yyVAL.expr = &ast.FuncExpr{Name: yyDollar[2].tok.Lit, Params: yyDollar[4].expr_idents, Stmt: yyDollar[8].compstmt, VarArg: true} + yyVAL.expr.SetPosition(yyDollar[1].tok.Position()) + } + case 69: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:504 + { + yyVAL.expr = &ast.ArrayExpr{} + if l, ok := yylex.(*Lexer); ok { + yyVAL.expr.SetPosition(l.pos) + } + } + case 70: + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:509 + { + yyVAL.expr = &ast.ArrayExpr{Exprs: yyDollar[3].exprs} + if l, ok := yylex.(*Lexer); ok { + yyVAL.expr.SetPosition(l.pos) + } + } + case 71: + yyDollar = yyS[yypt-7 : yypt+1] +//line parser.go.y:514 + { + yyVAL.expr = &ast.ArrayExpr{Exprs: yyDollar[5].exprs, TypeData: &ast.TypeStruct{Kind: ast.TypeSlice, SubType: yyDollar[2].type_data, Dimensions: yyDollar[1].slice_count}} + if l, ok := yylex.(*Lexer); ok { + yyVAL.expr.SetPosition(l.pos) + } + } + case 72: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:519 + { + yyVAL.expr = &ast.ParenExpr{SubExpr: yyDollar[2].expr} + if l, ok := yylex.(*Lexer); ok { + yyVAL.expr.SetPosition(l.pos) + } + } + case 73: + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:524 + { + yyVAL.expr = &ast.CallExpr{Name: yyDollar[1].tok.Lit, SubExprs: yyDollar[3].exprs, VarArg: true} + yyVAL.expr.SetPosition(yyDollar[1].tok.Position()) + } + case 74: + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:529 + { + yyVAL.expr = &ast.CallExpr{Name: yyDollar[1].tok.Lit, SubExprs: yyDollar[3].exprs} + yyVAL.expr.SetPosition(yyDollar[1].tok.Position()) + } + case 75: + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:534 + { + yyVAL.expr = &ast.AnonCallExpr{Expr: yyDollar[1].expr, SubExprs: yyDollar[3].exprs, VarArg: true} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 76: + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:539 + { + yyVAL.expr = &ast.AnonCallExpr{Expr: yyDollar[1].expr, SubExprs: yyDollar[3].exprs} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 77: + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:544 + { + yyVAL.expr = &ast.ItemExpr{Item: yyDollar[1].expr_ident, Index: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr_ident.Position()) + } + case 78: + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:549 + { + yyVAL.expr = &ast.ItemExpr{Item: yyDollar[1].expr, Index: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 79: + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:554 + { + yyVAL.expr = &ast.LenExpr{Expr: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].tok.Position()) + } + case 80: + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:559 + { + yyVAL.expr = &ast.ImportExpr{Name: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].tok.Position()) + } + case 81: + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:564 + { + if yyDollar[3].type_data.Kind == ast.TypeDefault { + yyDollar[3].type_data.Kind = ast.TypePtr + yyVAL.expr = &ast.MakeExpr{TypeData: yyDollar[3].type_data} + } else { + yyVAL.expr = &ast.MakeExpr{TypeData: &ast.TypeStruct{Kind: ast.TypePtr, SubType: yyDollar[3].type_data}} + } + yyVAL.expr.SetPosition(yyDollar[1].tok.Position()) + } + case 82: + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:574 + { + yyVAL.expr = &ast.MakeExpr{TypeData: yyDollar[3].type_data} + yyVAL.expr.SetPosition(yyDollar[1].tok.Position()) + } + case 83: + yyDollar = yyS[yypt-6 : yypt+1] +//line parser.go.y:579 + { + yyVAL.expr = &ast.MakeExpr{TypeData: yyDollar[3].type_data, LenExpr: yyDollar[5].expr} + yyVAL.expr.SetPosition(yyDollar[1].tok.Position()) + } + case 84: + yyDollar = yyS[yypt-8 : yypt+1] +//line parser.go.y:584 + { + yyVAL.expr = &ast.MakeExpr{TypeData: yyDollar[3].type_data, LenExpr: yyDollar[5].expr, CapExpr: yyDollar[7].expr} + yyVAL.expr.SetPosition(yyDollar[1].tok.Position()) + } + case 85: + yyDollar = yyS[yypt-7 : yypt+1] +//line parser.go.y:589 + { + yyVAL.expr = &ast.MakeTypeExpr{Name: yyDollar[4].tok.Lit, Type: yyDollar[6].expr} + yyVAL.expr.SetPosition(yyDollar[1].tok.Position()) + } + case 86: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:594 + { + yyVAL.expr = &ast.IncludeExpr{ItemExpr: yyDollar[1].expr, ListExpr: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 87: + yyDollar = yyS[yypt-6 : yypt+1] +//line parser.go.y:599 + { + yyDollar[4].expr_map.TypeData = &ast.TypeStruct{Kind: ast.TypeMap, Key: &ast.TypeStruct{Name: "interface"}, SubType: &ast.TypeStruct{Name: "interface"}} + yyVAL.expr = yyDollar[4].expr_map + yyVAL.expr.SetPosition(yyDollar[1].tok.Position()) + } + case 88: + yyDollar = yyS[yypt-10 : yypt+1] +//line parser.go.y:605 + { + yyDollar[8].expr_map.TypeData = &ast.TypeStruct{Kind: ast.TypeMap, Key: yyDollar[3].type_data, SubType: yyDollar[5].type_data} + yyVAL.expr = yyDollar[8].expr_map + yyVAL.expr.SetPosition(yyDollar[1].tok.Position()) + } + case 89: + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:611 + { + yyVAL.expr = yyDollar[3].expr_map + yyVAL.expr.SetPosition(yyDollar[3].expr_map.Position()) + } + case 90: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:616 + { + yyVAL.expr = yyDollar[1].expr_slice + yyVAL.expr.SetPosition(yyDollar[1].expr_slice.Position()) + } + case 91: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:621 + { + yyVAL.expr = yyDollar[1].expr_chan + yyVAL.expr.SetPosition(yyDollar[1].expr_chan.Position()) + } + case 95: + yyDollar = yyS[yypt-0 : yypt+1] +//line parser.go.y:630 + { + yyVAL.expr_idents = []string{} + } + case 96: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:634 + { + yyVAL.expr_idents = []string{yyDollar[1].tok.Lit} + } + case 97: + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:638 + { + if len(yyDollar[1].expr_idents) == 0 { + yylex.Error("syntax error: unexpected ','") + } + yyVAL.expr_idents = append(yyDollar[1].expr_idents, yyDollar[4].tok.Lit) + } + case 98: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:647 + { + yyVAL.type_data = &ast.TypeStruct{Name: yyDollar[1].tok.Lit} + } + case 99: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:651 + { + if yyDollar[1].type_data.Kind != ast.TypeDefault { + yylex.Error("not type default") + } else { + yyDollar[1].type_data.Env = append(yyDollar[1].type_data.Env, yyDollar[1].type_data.Name) + yyDollar[1].type_data.Name = yyDollar[3].tok.Lit + } + } + case 100: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:660 + { + if yyDollar[2].type_data.Kind == ast.TypeDefault { + yyDollar[2].type_data.Kind = ast.TypePtr + yyVAL.type_data = yyDollar[2].type_data + } else { + yyVAL.type_data = &ast.TypeStruct{Kind: ast.TypePtr, SubType: yyDollar[2].type_data} + } + } + case 101: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:669 + { + if yyDollar[2].type_data.Kind == ast.TypeDefault { + yyDollar[2].type_data.Kind = ast.TypeSlice + yyDollar[2].type_data.Dimensions = yyDollar[1].slice_count + yyVAL.type_data = yyDollar[2].type_data + } else { + yyVAL.type_data = &ast.TypeStruct{Kind: ast.TypeSlice, SubType: yyDollar[2].type_data, Dimensions: yyDollar[1].slice_count} + } + } + case 102: + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:679 + { + yyVAL.type_data = &ast.TypeStruct{Kind: ast.TypeMap, Key: yyDollar[3].type_data, SubType: yyDollar[5].type_data} + } + case 103: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:683 + { + if yyDollar[2].type_data.Kind == ast.TypeDefault { + yyDollar[2].type_data.Kind = ast.TypeChan + yyVAL.type_data = yyDollar[2].type_data + } else { + yyVAL.type_data = &ast.TypeStruct{Kind: ast.TypeChan, SubType: yyDollar[2].type_data} + } + } + case 104: + yyDollar = yyS[yypt-6 : yypt+1] +//line parser.go.y:692 + { + yyVAL.type_data = yyDollar[4].type_data_struct + } + case 105: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:698 + { + yyVAL.type_data_struct = &ast.TypeStruct{Kind: ast.TypeStructType, StructNames: []string{yyDollar[1].tok.Lit}, StructTypes: []*ast.TypeStruct{yyDollar[2].type_data}} + } + case 106: + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:702 + { + if yyDollar[1].type_data_struct == nil { + yylex.Error("syntax error: unexpected ','") + } + yyVAL.type_data_struct.StructNames = append(yyVAL.type_data_struct.StructNames, yyDollar[4].tok.Lit) + yyVAL.type_data_struct.StructTypes = append(yyVAL.type_data_struct.StructTypes, yyDollar[5].type_data) + } + case 107: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:712 + { + yyVAL.slice_count = 1 + } + case 108: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:716 + { + yyVAL.slice_count = yyDollar[3].slice_count + 1 + } + case 109: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:722 + { + yyVAL.expr_member_or_ident = yyDollar[1].expr_member + } + case 110: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:726 + { + yyVAL.expr_member_or_ident = yyDollar[1].expr_ident + } + case 111: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:732 + { + yyVAL.expr_member = &ast.MemberExpr{Expr: yyDollar[1].expr, Name: yyDollar[3].tok.Lit} + yyVAL.expr_member.SetPosition(yyDollar[1].expr.Position()) + } + case 112: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:739 + { + yyVAL.expr_ident = &ast.IdentExpr{Lit: yyDollar[1].tok.Lit} + yyVAL.expr_ident.SetPosition(yyDollar[1].tok.Position()) + } + case 113: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:746 + { + num, err := toNumber("-" + yyDollar[2].tok.Lit) + if err != nil { + yylex.Error("invalid number: -" + yyDollar[2].tok.Lit) + } + yyVAL.expr_literals = &ast.LiteralExpr{Literal: num} + yyVAL.expr_literals.SetPosition(yyDollar[2].tok.Position()) + } + case 114: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:755 + { + num, err := toNumber(yyDollar[1].tok.Lit) + if err != nil { + yylex.Error("invalid number: " + yyDollar[1].tok.Lit) + } + yyVAL.expr_literals = &ast.LiteralExpr{Literal: num} + yyVAL.expr_literals.SetPosition(yyDollar[1].tok.Position()) + } + case 115: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:764 + { + yyVAL.expr_literals = &ast.LiteralExpr{Literal: stringToValue(yyDollar[1].tok.Lit)} + yyVAL.expr_literals.SetPosition(yyDollar[1].tok.Position()) + } + case 116: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:769 + { + yyVAL.expr_literals = &ast.LiteralExpr{Literal: trueValue} + yyVAL.expr_literals.SetPosition(yyDollar[1].tok.Position()) + } + case 117: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:774 + { + yyVAL.expr_literals = &ast.LiteralExpr{Literal: falseValue} + yyVAL.expr_literals.SetPosition(yyDollar[1].tok.Position()) + } + case 118: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:779 + { + yyVAL.expr_literals = &ast.LiteralExpr{Literal: nilValue} + yyVAL.expr_literals.SetPosition(yyDollar[1].tok.Position()) + } + case 119: + yyDollar = yyS[yypt-0 : yypt+1] +//line parser.go.y:786 + { + yyVAL.expr_map = &ast.MapExpr{} + } + case 120: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:790 + { + yyVAL.expr_map = &ast.MapExpr{Keys: []ast.Expr{yyDollar[1].expr}, Values: []ast.Expr{yyDollar[3].expr}} + } + case 121: + yyDollar = yyS[yypt-6 : yypt+1] +//line parser.go.y:794 + { + if yyDollar[1].expr_map.Keys == nil { + yylex.Error("syntax error: unexpected ','") + } + yyVAL.expr_map.Keys = append(yyVAL.expr_map.Keys, yyDollar[4].expr) + yyVAL.expr_map.Values = append(yyVAL.expr_map.Values, yyDollar[6].expr) + } + case 122: + yyDollar = yyS[yypt-6 : yypt+1] +//line parser.go.y:804 + { + yyVAL.expr_slice = &ast.SliceExpr{Item: yyDollar[1].expr_ident, Begin: yyDollar[3].expr, End: yyDollar[5].expr} + } + case 123: + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:808 + { + yyVAL.expr_slice = &ast.SliceExpr{Item: yyDollar[1].expr_ident, Begin: yyDollar[3].expr, End: nil} + } + case 124: + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:812 + { + yyVAL.expr_slice = &ast.SliceExpr{Item: yyDollar[1].expr_ident, Begin: nil, End: yyDollar[4].expr} + } + case 125: + yyDollar = yyS[yypt-7 : yypt+1] +//line parser.go.y:816 + { + yyVAL.expr_slice = &ast.SliceExpr{Item: yyDollar[1].expr_ident, End: yyDollar[4].expr, Cap: yyDollar[6].expr} + } + case 126: + yyDollar = yyS[yypt-8 : yypt+1] +//line parser.go.y:820 + { + yyVAL.expr_slice = &ast.SliceExpr{Item: yyDollar[1].expr_ident, Begin: yyDollar[3].expr, End: yyDollar[5].expr, Cap: yyDollar[7].expr} + } + case 127: + yyDollar = yyS[yypt-6 : yypt+1] +//line parser.go.y:824 + { + yyVAL.expr_slice = &ast.SliceExpr{Item: yyDollar[1].expr, Begin: yyDollar[3].expr, End: yyDollar[5].expr} + } + case 128: + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:828 + { + yyVAL.expr_slice = &ast.SliceExpr{Item: yyDollar[1].expr, Begin: yyDollar[3].expr, End: nil} + } + case 129: + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:832 + { + yyVAL.expr_slice = &ast.SliceExpr{Item: yyDollar[1].expr, Begin: nil, End: yyDollar[4].expr} + } + case 130: + yyDollar = yyS[yypt-7 : yypt+1] +//line parser.go.y:836 + { + yyVAL.expr_slice = &ast.SliceExpr{Item: yyDollar[1].expr, End: yyDollar[4].expr, Cap: yyDollar[6].expr} + } + case 131: + yyDollar = yyS[yypt-8 : yypt+1] +//line parser.go.y:840 + { + yyVAL.expr_slice = &ast.SliceExpr{Item: yyDollar[1].expr, Begin: yyDollar[3].expr, End: yyDollar[5].expr, Cap: yyDollar[7].expr} + } + case 132: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:846 + { + yyVAL.expr_chan = &ast.ChanExpr{LHS: yyDollar[1].expr, RHS: yyDollar[3].expr} + } + case 133: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:850 + { + yyVAL.expr_chan = &ast.ChanExpr{RHS: yyDollar[2].expr} + } + case 134: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:856 + { + yyVAL.expr = &ast.UnaryExpr{Operator: "-", Expr: yyDollar[2].expr} + yyVAL.expr.SetPosition(yyDollar[2].expr.Position()) + } + case 135: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:861 + { + yyVAL.expr = &ast.UnaryExpr{Operator: "!", Expr: yyDollar[2].expr} + yyVAL.expr.SetPosition(yyDollar[2].expr.Position()) + } + case 136: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:866 + { + yyVAL.expr = &ast.UnaryExpr{Operator: "^", Expr: yyDollar[2].expr} + yyVAL.expr.SetPosition(yyDollar[2].expr.Position()) + } + case 137: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:871 + { + yyVAL.expr = &ast.AddrExpr{Expr: yyDollar[2].expr} + yyVAL.expr.SetPosition(yyDollar[2].expr.Position()) + } + case 138: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:876 + { + yyVAL.expr = &ast.DerefExpr{Expr: yyDollar[2].expr} + yyVAL.expr.SetPosition(yyDollar[2].expr.Position()) + } + case 139: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:883 + { + yyVAL.expr = &ast.OpExpr{Op: yyDollar[1].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 140: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:888 + { + yyVAL.expr = &ast.OpExpr{Op: yyDollar[1].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 141: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:893 + { + yyVAL.expr = &ast.OpExpr{Op: yyDollar[1].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 142: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:898 + { + yyVAL.expr = &ast.OpExpr{Op: yyDollar[1].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 143: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:905 + { + rhs := &ast.OpExpr{Op: &ast.AddOperator{LHS: yyDollar[1].expr, Operator: "+", RHS: oneLiteral}} + rhs.Op.SetPosition(yyDollar[1].expr.Position()) + rhs.SetPosition(yyDollar[1].expr.Position()) + yyVAL.expr = &ast.LetsExpr{LHSS: []ast.Expr{yyDollar[1].expr}, RHSS: []ast.Expr{rhs}} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 144: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:913 + { + rhs := &ast.OpExpr{Op: &ast.AddOperator{LHS: yyDollar[1].expr, Operator: "-", RHS: oneLiteral}} + rhs.Op.SetPosition(yyDollar[1].expr.Position()) + rhs.SetPosition(yyDollar[1].expr.Position()) + yyVAL.expr = &ast.LetsExpr{LHSS: []ast.Expr{yyDollar[1].expr}, RHSS: []ast.Expr{rhs}} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 145: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:921 + { + rhs := &ast.OpExpr{Op: &ast.AddOperator{LHS: yyDollar[1].expr, Operator: "+", RHS: yyDollar[3].expr}} + rhs.Op.SetPosition(yyDollar[1].expr.Position()) + rhs.SetPosition(yyDollar[1].expr.Position()) + yyVAL.expr = &ast.LetsExpr{LHSS: []ast.Expr{yyDollar[1].expr}, RHSS: []ast.Expr{rhs}} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 146: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:929 + { + rhs := &ast.OpExpr{Op: &ast.AddOperator{LHS: yyDollar[1].expr, Operator: "-", RHS: yyDollar[3].expr}} + rhs.Op.SetPosition(yyDollar[1].expr.Position()) + rhs.SetPosition(yyDollar[1].expr.Position()) + yyVAL.expr = &ast.LetsExpr{LHSS: []ast.Expr{yyDollar[1].expr}, RHSS: []ast.Expr{rhs}} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 147: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:937 + { + rhs := &ast.OpExpr{Op: &ast.AddOperator{LHS: yyDollar[1].expr, Operator: "|", RHS: yyDollar[3].expr}} + rhs.Op.SetPosition(yyDollar[1].expr.Position()) + rhs.SetPosition(yyDollar[1].expr.Position()) + yyVAL.expr = &ast.LetsExpr{LHSS: []ast.Expr{yyDollar[1].expr}, RHSS: []ast.Expr{rhs}} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 148: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:945 + { + rhs := &ast.OpExpr{Op: &ast.MultiplyOperator{LHS: yyDollar[1].expr, Operator: "*", RHS: yyDollar[3].expr}} + rhs.Op.SetPosition(yyDollar[1].expr.Position()) + rhs.SetPosition(yyDollar[1].expr.Position()) + yyVAL.expr = &ast.LetsExpr{LHSS: []ast.Expr{yyDollar[1].expr}, RHSS: []ast.Expr{rhs}} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 149: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:953 + { + rhs := &ast.OpExpr{Op: &ast.MultiplyOperator{LHS: yyDollar[1].expr, Operator: "/", RHS: yyDollar[3].expr}} + rhs.Op.SetPosition(yyDollar[1].expr.Position()) + rhs.SetPosition(yyDollar[1].expr.Position()) + yyVAL.expr = &ast.LetsExpr{LHSS: []ast.Expr{yyDollar[1].expr}, RHSS: []ast.Expr{rhs}} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 150: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:961 + { + rhs := &ast.OpExpr{Op: &ast.MultiplyOperator{LHS: yyDollar[1].expr, Operator: "&", RHS: yyDollar[3].expr}} + rhs.Op.SetPosition(yyDollar[1].expr.Position()) + rhs.SetPosition(yyDollar[1].expr.Position()) + yyVAL.expr = &ast.LetsExpr{LHSS: []ast.Expr{yyDollar[1].expr}, RHSS: []ast.Expr{rhs}} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 151: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:972 + { + yyVAL.expr = &ast.MultiplyOperator{LHS: yyDollar[1].expr, Operator: "*", RHS: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 152: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:977 + { + yyVAL.expr = &ast.MultiplyOperator{LHS: yyDollar[1].expr, Operator: "/", RHS: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 153: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:982 + { + yyVAL.expr = &ast.MultiplyOperator{LHS: yyDollar[1].expr, Operator: "%", RHS: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 154: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:987 + { + yyVAL.expr = &ast.MultiplyOperator{LHS: yyDollar[1].expr, Operator: "<<", RHS: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 155: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:992 + { + yyVAL.expr = &ast.MultiplyOperator{LHS: yyDollar[1].expr, Operator: ">>", RHS: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 156: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:997 + { + yyVAL.expr = &ast.MultiplyOperator{LHS: yyDollar[1].expr, Operator: "&", RHS: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 157: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:1004 + { + yyVAL.expr = &ast.AddOperator{LHS: yyDollar[1].expr, Operator: "+", RHS: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 158: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:1009 + { + yyVAL.expr = &ast.AddOperator{LHS: yyDollar[1].expr, Operator: "-", RHS: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 159: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:1014 + { + yyVAL.expr = &ast.AddOperator{LHS: yyDollar[1].expr, Operator: "|", RHS: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 160: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:1021 + { + yyVAL.expr = &ast.ComparisonOperator{LHS: yyDollar[1].expr, Operator: "==", RHS: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 161: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:1026 + { + yyVAL.expr = &ast.ComparisonOperator{LHS: yyDollar[1].expr, Operator: "!=", RHS: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 162: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:1031 + { + yyVAL.expr = &ast.ComparisonOperator{LHS: yyDollar[1].expr, Operator: "<", RHS: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 163: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:1036 + { + yyVAL.expr = &ast.ComparisonOperator{LHS: yyDollar[1].expr, Operator: "<=", RHS: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 164: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:1041 + { + yyVAL.expr = &ast.ComparisonOperator{LHS: yyDollar[1].expr, Operator: ">", RHS: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 165: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:1046 + { + yyVAL.expr = &ast.ComparisonOperator{LHS: yyDollar[1].expr, Operator: ">=", RHS: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 166: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:1053 + { + yyVAL.expr = &ast.BinaryOperator{LHS: yyDollar[1].expr, Operator: "&&", RHS: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + case 167: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:1058 + { + yyVAL.expr = &ast.BinaryOperator{LHS: yyDollar[1].expr, Operator: "||", RHS: yyDollar[3].expr} + yyVAL.expr.SetPosition(yyDollar[1].expr.Position()) + } + } + goto yystack /* stack new state and value */ +} diff --git a/src/tool/run/parser/parser.go.y b/src/tool/run/parser/parser.go.y new file mode 100644 index 0000000..c59d17a --- /dev/null +++ b/src/tool/run/parser/parser.go.y @@ -0,0 +1,1089 @@ +%{ +package parser + +import ( + "github.com/surdeus/goblin/src/tool/run/ast" +) + +%} + +%type compstmt +%type stmts +%type stmt +%type stmt_var_or_lets +%type stmt_var +%type stmt_lets +%type stmt_if +%type stmt_for +%type stmt_switch +%type stmt_switch_cases +%type stmt_switch_case +%type stmt_switch_default + +%type exprs +%type expr +%type expr_idents +%type type_data +%type type_data_struct +%type slice_count +%type expr_member_or_ident +%type expr_member +%type expr_ident +%type expr_literals +%type expr_map +%type expr_slice +%type expr_chan +%type expr_unary +%type expr_binary +%type expr_lets + +%type op_binary +%type op_comparison +%type op_add +%type op_multiply + +%union{ + tok ast.Token + + compstmt ast.Stmt + stmts ast.Stmt + stmt ast.Stmt + stmt_var_or_lets ast.Stmt + stmt_var ast.Stmt + stmt_lets ast.Stmt + stmt_if ast.Stmt + stmt_for ast.Stmt + stmt_switch ast.Stmt + stmt_switch_cases ast.Stmt + stmt_switch_case ast.Stmt + stmt_switch_default ast.Stmt + + exprs []ast.Expr + expr ast.Expr + expr_idents []string + type_data *ast.TypeStruct + type_data_struct *ast.TypeStruct + slice_count int + expr_member_or_ident ast.Expr + expr_member *ast.MemberExpr + expr_ident *ast.IdentExpr + expr_literals ast.Expr + expr_map *ast.MapExpr + expr_slice ast.Expr + expr_chan ast.Expr + expr_unary ast.Expr + expr_binary ast.Expr + expr_lets ast.Expr + + op_binary ast.Operator + op_comparison ast.Operator + op_add ast.Operator + op_multiply ast.Operator +} + +%token IDENT NUMBER STRING ARRAY VARARG FUNC RETURN VAR THROW IF ELSE FOR IN EQEQ NEQ GE LE OROR ANDAND NEW TRUE FALSE NIL NILCOALESCE MODULE TRY CATCH FINALLY PLUSEQ MINUSEQ MULEQ DIVEQ ANDEQ OREQ BREAK CONTINUE PLUSPLUS MINUSMINUS SHIFTLEFT SHIFTRIGHT SWITCH CASE DEFAULT GO CHAN STRUCT MAKE OPCHAN EQOPCHAN TYPE LEN DELETE CLOSE MAP IMPORT + +/* lowest precedence */ +%left , +%right '=' PLUSEQ MINUSEQ MULEQ DIVEQ ANDEQ OREQ EQOPCHAN +%right ':' +%right OPCHAN +%right '?' NILCOALESCE +%left OROR +%left ANDAND +%left EQEQ NEQ '<' LE '>' GE +%left '+' '-' '|' '^' +%left '*' '/' '%' SHIFTLEFT SHIFTRIGHT '&' +%right IN +%right PLUSPLUS MINUSMINUS +%right UNARY +/* highest precedence */ +/* https://golang.org/ref/spec#Expression */ + + +%% + +compstmt : + opt_term + { + $$ = nil + } + | stmts opt_term + { + $$ = $1 + } + +stmts : + opt_term stmt + { + if $2 != nil { + $$ = &ast.StmtsStmt{Stmts: []ast.Stmt{$2}} + } + if l, ok := yylex.(*Lexer); ok { + l.stmt = $$ + } + } + | stmts term stmt + { + if $3 != nil { + if $1 == nil { + $$ = &ast.StmtsStmt{Stmts: []ast.Stmt{$3}} + } else { + stmts := $1.(*ast.StmtsStmt) + stmts.Stmts = append(stmts.Stmts, $3) + } + if l, ok := yylex.(*Lexer); ok { + l.stmt = $$ + } + } + } + +stmt : + /* nothing */ + { + $$ = nil + } + | stmt_var_or_lets + { + $$ = $1 + } + | BREAK + { + $$ = &ast.BreakStmt{} + $$.SetPosition($1.Position()) + } + | CONTINUE + { + $$ = &ast.ContinueStmt{} + $$.SetPosition($1.Position()) + } + | RETURN exprs + { + $$ = &ast.ReturnStmt{Exprs: $2} + $$.SetPosition($1.Position()) + } + | THROW expr + { + $$ = &ast.ThrowStmt{Expr: $2} + $$.SetPosition($1.Position()) + } + | MODULE IDENT '{' compstmt '}' + { + $$ = &ast.ModuleStmt{Name: $2.Lit, Stmt: $4} + $$.SetPosition($1.Position()) + } + | TRY '{' compstmt '}' CATCH IDENT '{' compstmt '}' FINALLY '{' compstmt '}' + { + $$ = &ast.TryStmt{Try: $3, Var: $6.Lit, Catch: $8, Finally: $12} + $$.SetPosition($1.Position()) + } + | TRY '{' compstmt '}' CATCH '{' compstmt '}' FINALLY '{' compstmt '}' + { + $$ = &ast.TryStmt{Try: $3, Catch: $7, Finally: $11} + $$.SetPosition($1.Position()) + } + | TRY '{' compstmt '}' CATCH IDENT '{' compstmt '}' + { + $$ = &ast.TryStmt{Try: $3, Var: $6.Lit, Catch: $8} + $$.SetPosition($1.Position()) + } + | TRY '{' compstmt '}' CATCH '{' compstmt '}' + { + $$ = &ast.TryStmt{Try: $3, Catch: $7} + $$.SetPosition($1.Position()) + } + | GO IDENT '(' exprs VARARG ')' + { + $$ = &ast.GoroutineStmt{Expr: &ast.CallExpr{Name: $2.Lit, SubExprs: $4, VarArg: true, Go: true}} + $$.SetPosition($2.Position()) + } + | GO IDENT '(' exprs ')' + { + $$ = &ast.GoroutineStmt{Expr: &ast.CallExpr{Name: $2.Lit, SubExprs: $4, Go: true}} + $$.SetPosition($2.Position()) + } + | GO expr '(' exprs VARARG ')' + { + $$ = &ast.GoroutineStmt{Expr: &ast.AnonCallExpr{Expr: $2, SubExprs: $4, VarArg: true, Go: true}} + $$.SetPosition($2.Position()) + } + | GO expr '(' exprs ')' + { + $$ = &ast.GoroutineStmt{Expr: &ast.AnonCallExpr{Expr: $2, SubExprs: $4, Go: true}} + $$.SetPosition($1.Position()) + } + | DELETE '(' expr ')' + { + $$ = &ast.DeleteStmt{Item: $3} + $$.SetPosition($1.Position()) + } + | DELETE '(' expr ',' expr ')' + { + $$ = &ast.DeleteStmt{Item: $3, Key: $5} + $$.SetPosition($1.Position()) + } + | CLOSE '(' expr ')' + { + $$ = &ast.CloseStmt{Expr: $3} + $$.SetPosition($1.Position()) + } + | stmt_if + { + $$ = $1 + } + | stmt_for + { + $$ = $1 + } + | stmt_switch + { + $$ = $1 + } + | expr + { + $$ = &ast.ExprStmt{Expr: $1} + $$.SetPosition($1.Position()) + } + +stmt_var_or_lets : + stmt_var + { + $$ = $1 + } + | stmt_lets + { + $$ = $1 + } + +stmt_var : + VAR expr_idents '=' exprs + { + $$ = &ast.VarStmt{Names: $2, Exprs: $4} + $$.SetPosition($1.Position()) + } + +stmt_lets : + expr '=' expr + { + $$ = &ast.LetsStmt{LHSS: []ast.Expr{$1}, RHSS: []ast.Expr{$3}} + $$.SetPosition($1.Position()) + } + | exprs '=' exprs + { + if len($1) == 2 && len($3) == 1 { + if _, ok := $3[0].(*ast.ItemExpr); ok { + $$ = &ast.LetMapItemStmt{LHSS: $1, RHS: $3[0]} + } else { + $$ = &ast.LetsStmt{LHSS: $1, RHSS: $3} + } + } else { + $$ = &ast.LetsStmt{LHSS: $1, RHSS: $3} + } + } + | expr EQOPCHAN expr + { + $$ = &ast.ChanStmt{LHS: $1, RHS: $3} + $$.SetPosition($1.Position()) + } + | exprs EQOPCHAN expr + { + if len($1) == 2 { + chanStmt := &ast.ChanStmt{LHS: $1[0].(ast.Expr), OkExpr: $1[1].(ast.Expr), RHS: $3} + $$ = chanStmt + $$.SetPosition(chanStmt.LHS.Position()) + } else if len($1) < 2 { + yylex.Error("missing expressions on left side of channel operator") + $$ = &ast.ChanStmt{RHS: $3} + $$.SetPosition($2.Position()) + } + } + +stmt_if : + IF expr '{' compstmt '}' + { + $$ = &ast.IfStmt{If: $2, Then: $4, Else: nil} + $$.SetPosition($1.Position()) + } + | stmt_if ELSE IF expr '{' compstmt '}' + { + ifStmt := $1.(*ast.IfStmt) + ifStmt.ElseIf = append(ifStmt.ElseIf, &ast.IfStmt{If: $4, Then: $6}) + } + | stmt_if ELSE '{' compstmt '}' + { + ifStmt := $1.(*ast.IfStmt) + if ifStmt.Else != nil { + yylex.Error("multiple else statement") + } + ifStmt.Else = $4 + } + +stmt_for : + FOR '{' compstmt '}' + { + $$ = &ast.LoopStmt{Stmt: $3} + $$.SetPosition($1.Position()) + } + | FOR expr_idents IN expr '{' compstmt '}' + { + if len($2) < 1 { + yylex.Error("missing identifier") + } else if len($2) > 2 { + yylex.Error("too many identifiers") + } else { + $$ = &ast.ForStmt{Vars: $2, Value: $4, Stmt: $6} + $$.SetPosition($1.Position()) + } + } + | FOR expr '{' compstmt '}' + { + $$ = &ast.LoopStmt{Expr: $2, Stmt: $4} + $$.SetPosition($1.Position()) + } + | FOR ';' ';' '{' compstmt '}' + { + $$ = &ast.CForStmt{Stmt: $5} + $$.SetPosition($1.Position()) + } + | FOR ';' ';' expr '{' compstmt '}' + { + $$ = &ast.CForStmt{Expr3: $4, Stmt: $6} + $$.SetPosition($1.Position()) + } + | FOR ';' expr ';' '{' compstmt '}' + { + $$ = &ast.CForStmt{Expr2: $3, Stmt: $6} + $$.SetPosition($1.Position()) + } + | FOR ';' expr ';' expr '{' compstmt '}' + { + $$ = &ast.CForStmt{Expr2: $3, Expr3: $5, Stmt: $7} + $$.SetPosition($1.Position()) + } + | FOR stmt_var_or_lets ';' ';' '{' compstmt '}' + { + $$ = &ast.CForStmt{Stmt1: $2, Stmt: $6} + $$.SetPosition($1.Position()) + } + | FOR stmt_var_or_lets ';' ';' expr '{' compstmt '}' + { + $$ = &ast.CForStmt{Stmt1: $2, Expr3: $5, Stmt: $7} + $$.SetPosition($1.Position()) + } + | FOR stmt_var_or_lets ';' expr ';' '{' compstmt '}' + { + $$ = &ast.CForStmt{Stmt1: $2, Expr2: $4, Stmt: $7} + $$.SetPosition($1.Position()) + } + | FOR stmt_var_or_lets ';' expr ';' expr '{' compstmt '}' + { + $$ = &ast.CForStmt{Stmt1: $2, Expr2: $4, Expr3: $6, Stmt: $8} + $$.SetPosition($1.Position()) + } + +stmt_switch : + SWITCH expr '{' opt_newlines stmt_switch_cases opt_newlines '}' + { + switchStmt := $5.(*ast.SwitchStmt) + switchStmt.Expr = $2 + $$ = switchStmt + $$.SetPosition($1.Position()) + } + +stmt_switch_cases : + /* nothing */ + { + $$ = &ast.SwitchStmt{} + } + | stmt_switch_default + { + $$ = &ast.SwitchStmt{Default: $1} + } + | stmt_switch_case + { + $$ = &ast.SwitchStmt{Cases: []ast.Stmt{$1}} + } + | stmt_switch_cases stmt_switch_case + { + switchStmt := $1.(*ast.SwitchStmt) + switchStmt.Cases = append(switchStmt.Cases, $2) + $$ = switchStmt + } + | stmt_switch_cases stmt_switch_default + { + switchStmt := $1.(*ast.SwitchStmt) + if switchStmt.Default != nil { + yylex.Error("multiple default statement") + } + switchStmt.Default = $2 + } + +stmt_switch_case : + CASE expr ':' compstmt + { + $$ = &ast.SwitchCaseStmt{Exprs: []ast.Expr{$2}, Stmt: $4} + $$.SetPosition($1.Position()) + } + | CASE exprs ':' compstmt + { + $$ = &ast.SwitchCaseStmt{Exprs: $2, Stmt: $4} + $$.SetPosition($1.Position()) + } + +stmt_switch_default : + DEFAULT ':' compstmt + { + $$ = $3 + } + + +exprs : + /* nothing */ + { + $$ = nil + } + | expr + { + $$ = []ast.Expr{$1} + } + | exprs ',' opt_newlines expr + { + if len($1) == 0 { + yylex.Error("syntax error: unexpected ','") + } + $$ = append($1, $4) + } + | exprs ',' opt_newlines expr_ident + { + if len($1) == 0 { + yylex.Error("syntax error: unexpected ','") + } + $$ = append($1, $4) + } + +expr : + expr_member_or_ident + { + $$ = $1 + } + | expr_literals + { + $$ = $1 + } + | expr '?' expr ':' expr + { + $$ = &ast.TernaryOpExpr{Expr: $1, LHS: $3, RHS: $5} + $$.SetPosition($1.Position()) + } + | expr NILCOALESCE expr + { + $$ = &ast.NilCoalescingOpExpr{LHS: $1, RHS: $3} + $$.SetPosition($1.Position()) + } + | FUNC '(' expr_idents ')' '{' compstmt '}' + { + $$ = &ast.FuncExpr{Params: $3, Stmt: $6} + $$.SetPosition($1.Position()) + } + | FUNC '(' expr_idents VARARG ')' '{' compstmt '}' + { + $$ = &ast.FuncExpr{Params: $3, Stmt: $7, VarArg: true} + $$.SetPosition($1.Position()) + } + | FUNC IDENT '(' expr_idents ')' '{' compstmt '}' + { + $$ = &ast.FuncExpr{Name: $2.Lit, Params: $4, Stmt: $7} + $$.SetPosition($1.Position()) + } + | FUNC IDENT '(' expr_idents VARARG ')' '{' compstmt '}' + { + $$ = &ast.FuncExpr{Name: $2.Lit, Params: $4, Stmt: $8, VarArg: true} + $$.SetPosition($1.Position()) + } + | '[' ']' + { + $$ = &ast.ArrayExpr{} + if l, ok := yylex.(*Lexer); ok { $$.SetPosition(l.pos) } + } + | '[' opt_newlines exprs opt_comma_newlines ']' + { + $$ = &ast.ArrayExpr{Exprs: $3} + if l, ok := yylex.(*Lexer); ok { $$.SetPosition(l.pos) } + } + | slice_count type_data '{' opt_newlines exprs opt_comma_newlines '}' + { + $$ = &ast.ArrayExpr{Exprs: $5, TypeData: &ast.TypeStruct{Kind: ast.TypeSlice, SubType: $2, Dimensions: $1}} + if l, ok := yylex.(*Lexer); ok { $$.SetPosition(l.pos) } + } + | '(' expr ')' + { + $$ = &ast.ParenExpr{SubExpr: $2} + if l, ok := yylex.(*Lexer); ok { $$.SetPosition(l.pos) } + } + | IDENT '(' exprs VARARG ')' + { + $$ = &ast.CallExpr{Name: $1.Lit, SubExprs: $3, VarArg: true} + $$.SetPosition($1.Position()) + } + | IDENT '(' exprs ')' + { + $$ = &ast.CallExpr{Name: $1.Lit, SubExprs: $3} + $$.SetPosition($1.Position()) + } + | expr '(' exprs VARARG ')' + { + $$ = &ast.AnonCallExpr{Expr: $1, SubExprs: $3, VarArg: true} + $$.SetPosition($1.Position()) + } + | expr '(' exprs ')' + { + $$ = &ast.AnonCallExpr{Expr: $1, SubExprs: $3} + $$.SetPosition($1.Position()) + } + | expr_ident '[' expr ']' + { + $$ = &ast.ItemExpr{Item: $1, Index: $3} + $$.SetPosition($1.Position()) + } + | expr '[' expr ']' + { + $$ = &ast.ItemExpr{Item: $1, Index: $3} + $$.SetPosition($1.Position()) + } + | LEN '(' expr ')' + { + $$ = &ast.LenExpr{Expr: $3} + $$.SetPosition($1.Position()) + } + | IMPORT '(' expr ')' + { + $$ = &ast.ImportExpr{Name: $3} + $$.SetPosition($1.Position()) + } + | NEW '(' type_data ')' + { + if $3.Kind == ast.TypeDefault { + $3.Kind = ast.TypePtr + $$ = &ast.MakeExpr{TypeData: $3} + } else { + $$ = &ast.MakeExpr{TypeData: &ast.TypeStruct{Kind: ast.TypePtr, SubType: $3}} + } + $$.SetPosition($1.Position()) + } + | MAKE '(' type_data ')' + { + $$ = &ast.MakeExpr{TypeData: $3} + $$.SetPosition($1.Position()) + } + | MAKE '(' type_data ',' expr ')' + { + $$ = &ast.MakeExpr{TypeData: $3, LenExpr: $5} + $$.SetPosition($1.Position()) + } + | MAKE '(' type_data ',' expr ',' expr ')' + { + $$ = &ast.MakeExpr{TypeData: $3, LenExpr: $5, CapExpr: $7} + $$.SetPosition($1.Position()) + } + | MAKE '(' TYPE IDENT ',' expr ')' + { + $$ = &ast.MakeTypeExpr{Name: $4.Lit, Type: $6} + $$.SetPosition($1.Position()) + } + | expr IN expr + { + $$ = &ast.IncludeExpr{ItemExpr: $1, ListExpr: $3} + $$.SetPosition($1.Position()) + } + | MAP '{' opt_newlines expr_map opt_comma_newlines '}' + { + $4.TypeData = &ast.TypeStruct{Kind: ast.TypeMap, Key: &ast.TypeStruct{Name: "interface"}, SubType: &ast.TypeStruct{Name: "interface"}} + $$ = $4 + $$.SetPosition($1.Position()) + } + | MAP '[' type_data ']' type_data '{' opt_newlines expr_map opt_comma_newlines '}' + { + $8.TypeData = &ast.TypeStruct{Kind: ast.TypeMap, Key: $3, SubType: $5} + $$ = $8 + $$.SetPosition($1.Position()) + } + | '{' opt_newlines expr_map opt_comma_newlines '}' + { + $$ = $3 + $$.SetPosition($3.Position()) + } + | expr_slice + { + $$ = $1 + $$.SetPosition($1.Position()) + } + | expr_chan + { + $$ = $1 + $$.SetPosition($1.Position()) + } + | expr_unary + | expr_binary + | expr_lets + +expr_idents : + { + $$ = []string{} + } + | IDENT + { + $$ = []string{$1.Lit} + } + | expr_idents ',' opt_newlines IDENT + { + if len($1) == 0 { + yylex.Error("syntax error: unexpected ','") + } + $$ = append($1, $4.Lit) + } + +type_data : + IDENT + { + $$ = &ast.TypeStruct{Name: $1.Lit} + } + | type_data '.' IDENT + { + if $1.Kind != ast.TypeDefault { + yylex.Error("not type default") + } else { + $1.Env = append($1.Env, $1.Name) + $1.Name = $3.Lit + } + } + | '*' type_data + { + if $2.Kind == ast.TypeDefault { + $2.Kind = ast.TypePtr + $$ = $2 + } else { + $$ = &ast.TypeStruct{Kind: ast.TypePtr, SubType: $2} + } + } + | slice_count type_data + { + if $2.Kind == ast.TypeDefault { + $2.Kind = ast.TypeSlice + $2.Dimensions = $1 + $$ = $2 + } else { + $$ = &ast.TypeStruct{Kind: ast.TypeSlice, SubType: $2, Dimensions: $1} + } + } + | MAP '[' type_data ']' type_data + { + $$ = &ast.TypeStruct{Kind: ast.TypeMap, Key: $3, SubType: $5} + } + | CHAN type_data + { + if $2.Kind == ast.TypeDefault { + $2.Kind = ast.TypeChan + $$ = $2 + } else { + $$ = &ast.TypeStruct{Kind: ast.TypeChan, SubType: $2} + } + } + | STRUCT '{' opt_newlines type_data_struct opt_newlines '}' + { + $$ = $4 + } + +type_data_struct : + IDENT type_data + { + $$ = &ast.TypeStruct{Kind: ast.TypeStructType, StructNames: []string{$1.Lit}, StructTypes: []*ast.TypeStruct{$2}} + } + | type_data_struct ',' opt_newlines IDENT type_data + { + if $1 == nil { + yylex.Error("syntax error: unexpected ','") + } + $$.StructNames = append($$.StructNames, $4.Lit) + $$.StructTypes = append($$.StructTypes, $5) + } + +slice_count : + '[' ']' + { + $$ = 1 + } + | '[' ']' slice_count + { + $$ = $3 + 1 + } + +expr_member_or_ident : + expr_member + { + $$ = $1 + } + | expr_ident + { + $$ = $1 + } + +expr_member : + expr '.' IDENT + { + $$ = &ast.MemberExpr{Expr: $1, Name: $3.Lit} + $$.SetPosition($1.Position()) + } + +expr_ident : + IDENT + { + $$ = &ast.IdentExpr{Lit: $1.Lit} + $$.SetPosition($1.Position()) + } + +expr_literals : + '-' NUMBER + { + num, err := toNumber("-" + $2.Lit) + if err != nil { + yylex.Error("invalid number: -" + $2.Lit) + } + $$ = &ast.LiteralExpr{Literal: num} + $$.SetPosition($2.Position()) + } + | NUMBER + { + num, err := toNumber($1.Lit) + if err != nil { + yylex.Error("invalid number: " + $1.Lit) + } + $$ = &ast.LiteralExpr{Literal: num} + $$.SetPosition($1.Position()) + } + | STRING + { + $$ = &ast.LiteralExpr{Literal: stringToValue($1.Lit)} + $$.SetPosition($1.Position()) + } + | TRUE + { + $$ = &ast.LiteralExpr{Literal: trueValue} + $$.SetPosition($1.Position()) + } + | FALSE + { + $$ = &ast.LiteralExpr{Literal: falseValue} + $$.SetPosition($1.Position()) + } + | NIL + { + $$ = &ast.LiteralExpr{Literal: nilValue} + $$.SetPosition($1.Position()) + } + +expr_map : + /* nothing */ + { + $$ = &ast.MapExpr{} + } + | expr ':' expr + { + $$ = &ast.MapExpr{Keys: []ast.Expr{$1}, Values: []ast.Expr{$3}} + } + | expr_map ',' opt_newlines expr ':' expr + { + if $1.Keys == nil { + yylex.Error("syntax error: unexpected ','") + } + $$.Keys = append($$.Keys, $4) + $$.Values = append($$.Values, $6) + } + +expr_slice : + expr_ident '[' expr ':' expr ']' + { + $$ = &ast.SliceExpr{Item: $1, Begin: $3, End: $5} + } + | expr_ident '[' expr ':' ']' + { + $$ = &ast.SliceExpr{Item: $1, Begin: $3, End: nil} + } + | expr_ident '[' ':' expr ']' + { + $$ = &ast.SliceExpr{Item: $1, Begin: nil, End: $4} + } + | expr_ident '[' ':' expr ':' expr ']' + { + $$ = &ast.SliceExpr{Item: $1, End: $4, Cap: $6} + } + | expr_ident '[' expr ':' expr ':' expr ']' + { + $$ = &ast.SliceExpr{Item: $1, Begin: $3, End: $5, Cap: $7} + } + | expr '[' expr ':' expr ']' + { + $$ = &ast.SliceExpr{Item: $1, Begin: $3, End: $5} + } + | expr '[' expr ':' ']' + { + $$ = &ast.SliceExpr{Item: $1, Begin: $3, End: nil} + } + | expr '[' ':' expr ']' + { + $$ = &ast.SliceExpr{Item: $1, Begin: nil, End: $4} + } + | expr '[' ':' expr ':' expr ']' + { + $$ = &ast.SliceExpr{Item: $1, End: $4, Cap: $6} + } + | expr '[' expr ':' expr ':' expr ']' + { + $$ = &ast.SliceExpr{Item: $1, Begin: $3, End: $5, Cap: $7} + } + +expr_chan : + expr OPCHAN expr + { + $$ = &ast.ChanExpr{LHS: $1, RHS: $3} + } + | OPCHAN expr + { + $$ = &ast.ChanExpr{RHS: $2} + } + +expr_unary : + '-' expr %prec UNARY + { + $$ = &ast.UnaryExpr{Operator: "-", Expr: $2} + $$.SetPosition($2.Position()) + } + | '!' expr %prec UNARY + { + $$ = &ast.UnaryExpr{Operator: "!", Expr: $2} + $$.SetPosition($2.Position()) + } + | '^' expr %prec UNARY + { + $$ = &ast.UnaryExpr{Operator: "^", Expr: $2} + $$.SetPosition($2.Position()) + } + | '&' expr %prec UNARY + { + $$ = &ast.AddrExpr{Expr: $2} + $$.SetPosition($2.Position()) + } + | '*' expr %prec UNARY + { + $$ = &ast.DerefExpr{Expr: $2} + $$.SetPosition($2.Position()) + } + +expr_binary : + op_multiply + { + $$ = &ast.OpExpr{Op: $1} + $$.SetPosition($1.Position()) + } + | op_add + { + $$ = &ast.OpExpr{Op: $1} + $$.SetPosition($1.Position()) + } + | op_comparison + { + $$ = &ast.OpExpr{Op: $1} + $$.SetPosition($1.Position()) + } + | op_binary + { + $$ = &ast.OpExpr{Op: $1} + $$.SetPosition($1.Position()) + } + +expr_lets: + expr PLUSPLUS + { + rhs := &ast.OpExpr{Op: &ast.AddOperator{LHS: $1, Operator: "+", RHS: oneLiteral}} + rhs.Op.SetPosition($1.Position()) + rhs.SetPosition($1.Position()) + $$ = &ast.LetsExpr{LHSS: []ast.Expr{$1}, RHSS: []ast.Expr{rhs}} + $$.SetPosition($1.Position()) + } + | expr MINUSMINUS + { + rhs := &ast.OpExpr{Op: &ast.AddOperator{LHS: $1, Operator: "-", RHS: oneLiteral}} + rhs.Op.SetPosition($1.Position()) + rhs.SetPosition($1.Position()) + $$ = &ast.LetsExpr{LHSS: []ast.Expr{$1}, RHSS: []ast.Expr{rhs}} + $$.SetPosition($1.Position()) + } + | expr PLUSEQ expr + { + rhs := &ast.OpExpr{Op: &ast.AddOperator{LHS: $1, Operator: "+", RHS: $3}} + rhs.Op.SetPosition($1.Position()) + rhs.SetPosition($1.Position()) + $$ = &ast.LetsExpr{LHSS: []ast.Expr{$1}, RHSS: []ast.Expr{rhs}} + $$.SetPosition($1.Position()) + } + | expr MINUSEQ expr + { + rhs := &ast.OpExpr{Op: &ast.AddOperator{LHS: $1, Operator: "-", RHS: $3}} + rhs.Op.SetPosition($1.Position()) + rhs.SetPosition($1.Position()) + $$ = &ast.LetsExpr{LHSS: []ast.Expr{$1}, RHSS: []ast.Expr{rhs}} + $$.SetPosition($1.Position()) + } + | expr OREQ expr + { + rhs := &ast.OpExpr{Op: &ast.AddOperator{LHS: $1, Operator: "|", RHS: $3}} + rhs.Op.SetPosition($1.Position()) + rhs.SetPosition($1.Position()) + $$ = &ast.LetsExpr{LHSS: []ast.Expr{$1}, RHSS: []ast.Expr{rhs}} + $$.SetPosition($1.Position()) + } + | expr MULEQ expr + { + rhs := &ast.OpExpr{Op: &ast.MultiplyOperator{LHS: $1, Operator: "*", RHS: $3}} + rhs.Op.SetPosition($1.Position()) + rhs.SetPosition($1.Position()) + $$ = &ast.LetsExpr{LHSS: []ast.Expr{$1}, RHSS: []ast.Expr{rhs}} + $$.SetPosition($1.Position()) + } + | expr DIVEQ expr + { + rhs := &ast.OpExpr{Op: &ast.MultiplyOperator{LHS: $1, Operator: "/", RHS: $3}} + rhs.Op.SetPosition($1.Position()) + rhs.SetPosition($1.Position()) + $$ = &ast.LetsExpr{LHSS: []ast.Expr{$1}, RHSS: []ast.Expr{rhs}} + $$.SetPosition($1.Position()) + } + | expr ANDEQ expr + { + rhs := &ast.OpExpr{Op: &ast.MultiplyOperator{LHS: $1, Operator: "&", RHS: $3}} + rhs.Op.SetPosition($1.Position()) + rhs.SetPosition($1.Position()) + $$ = &ast.LetsExpr{LHSS: []ast.Expr{$1}, RHSS: []ast.Expr{rhs}} + $$.SetPosition($1.Position()) + } + + +op_multiply : + expr '*' expr + { + $$ = &ast.MultiplyOperator{LHS: $1, Operator: "*", RHS: $3} + $$.SetPosition($1.Position()) + } + | expr '/' expr + { + $$ = &ast.MultiplyOperator{LHS: $1, Operator: "/", RHS: $3} + $$.SetPosition($1.Position()) + } + | expr '%' expr + { + $$ = &ast.MultiplyOperator{LHS: $1, Operator: "%", RHS: $3} + $$.SetPosition($1.Position()) + } + | expr SHIFTLEFT expr + { + $$ = &ast.MultiplyOperator{LHS: $1, Operator: "<<", RHS: $3} + $$.SetPosition($1.Position()) + } + | expr SHIFTRIGHT expr + { + $$ = &ast.MultiplyOperator{LHS: $1, Operator: ">>", RHS: $3} + $$.SetPosition($1.Position()) + } + | expr '&' expr + { + $$ = &ast.MultiplyOperator{LHS: $1, Operator: "&", RHS: $3} + $$.SetPosition($1.Position()) + } + +op_add : + expr '+' expr + { + $$ = &ast.AddOperator{LHS: $1, Operator: "+", RHS: $3} + $$.SetPosition($1.Position()) + } + | expr '-' expr + { + $$ = &ast.AddOperator{LHS: $1, Operator: "-", RHS: $3} + $$.SetPosition($1.Position()) + } + | expr '|' expr + { + $$ = &ast.AddOperator{LHS: $1, Operator: "|", RHS: $3} + $$.SetPosition($1.Position()) + } + +op_comparison : + expr EQEQ expr + { + $$ = &ast.ComparisonOperator{LHS: $1, Operator: "==", RHS: $3} + $$.SetPosition($1.Position()) + } + | expr NEQ expr + { + $$ = &ast.ComparisonOperator{LHS: $1, Operator: "!=", RHS: $3} + $$.SetPosition($1.Position()) + } + | expr '<' expr + { + $$ = &ast.ComparisonOperator{LHS: $1, Operator: "<", RHS: $3} + $$.SetPosition($1.Position()) + } + | expr LE expr + { + $$ = &ast.ComparisonOperator{LHS: $1, Operator: "<=", RHS: $3} + $$.SetPosition($1.Position()) + } + | expr '>' expr + { + $$ = &ast.ComparisonOperator{LHS: $1, Operator: ">", RHS: $3} + $$.SetPosition($1.Position()) + } + | expr GE expr + { + $$ = &ast.ComparisonOperator{LHS: $1, Operator: ">=", RHS: $3} + $$.SetPosition($1.Position()) + } + +op_binary : + expr ANDAND expr + { + $$ = &ast.BinaryOperator{LHS: $1, Operator: "&&", RHS: $3} + $$.SetPosition($1.Position()) + } + | expr OROR expr + { + $$ = &ast.BinaryOperator{LHS: $1, Operator: "||", RHS: $3} + $$.SetPosition($1.Position()) + } + + +opt_term : + /* nothing */ + | term + +term : + ';' newlines + | newlines + | ';' + +opt_newlines : + /* nothing */ + | newlines + +newlines : + newline + | newlines newline + +newline : '\n' + +opt_comma_newlines : + /* nothing */ + | ',' newlines + | newlines + | ',' + +%% diff --git a/src/tool/run/vm/doc.go b/src/tool/run/vm/doc.go new file mode 100644 index 0000000..6bbb194 --- /dev/null +++ b/src/tool/run/vm/doc.go @@ -0,0 +1,2 @@ +// Package vm implements virtual-machine for anko. +package vm diff --git a/src/tool/run/vm/example_containers_test.go b/src/tool/run/vm/example_containers_test.go new file mode 100644 index 0000000..b1fa63e --- /dev/null +++ b/src/tool/run/vm/example_containers_test.go @@ -0,0 +1,259 @@ +package vm_test + +import ( + "fmt" + "log" + + "github.com/surdeus/goblin/src/tool/run/env" + "github.com/surdeus/goblin/src/tool/run/vm" +) + +func Example_vmArrays() { + e := env.NewEnv() + + err := e.Define("println", fmt.Println) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + + script := ` +a = []interface{1, 2} +println(a) + +a += 3 +println(a) + +a = []interface{} +// this automatically appends to array +a[0] = 1 +println(a) + +println("") + +a = []interface{} +// this would give an index out of range error +// a[1] = 1 + +a = []interface{1, 2} +b = []interface{3, 4} +c = a + b +println(c) + +c = []interface{1, 2} + []interface{3, 4} +println(c) + +println("") + +c = []interface{a} + b +println(c) + +c = []interface{a} + []interface{b} +println(c) + +c = []interface{[]interface{1, 2}} + []interface{[]interface{3, 4}} +println(c) + +println("") + +a = []interface{1, 2} + +println(len(a)) + +println(a[1]) + +a = [1, 2] +println(a) +` + + _, err = vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("execute error: %v\n", err) + } + + // output: + // [1 2] + // [1 2 3] + // [1] + // + // [1 2 3 4] + // [1 2 3 4] + // + // [[1 2] 3 4] + // [[1 2] [3 4]] + // [[1 2] [3 4]] + // + // 2 + // 2 + // [1 2] +} + +func Example_vmMaps() { + e := env.NewEnv() + + err := e.Define("println", fmt.Println) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + + script := ` +a = map[interface]interface{} +println(a) + +a.b = 1 +println(a) +println(a.b) + +a["b"] = 2 +println(a["b"]) + +println(len(a)) + +println("") + +b, ok = a["b"] +println(b) +println(ok) + +delete(a, "b") + +_, ok = a["b"] +println(ok) + +println("") + +a = {} +println(a) + +a.b = 1 +println(a) +println(a.b) + +a["b"] = 2 +println(a["b"]) + +` + + _, err = vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("execute error: %v\n", err) + } + + // output: + // map[] + // map[b:1] + // 1 + // 2 + // 1 + // + // 2 + // true + // false + // + // map[] + // map[b:1] + // 1 + // 2 +} + +func Example_vmStructs() { + e := env.NewEnv() + + err := e.Define("println", fmt.Println) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + + script := ` +a = make(struct { + A int64, + B float64 +}) +println(a) + +a.A = 1 +println(a) +println(a.A) + +a.B = 2.5 +println(a) +println(a.B) +` + + _, err = vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("execute error: %v\n", err) + } + + // output: + // {0 0} + // {1 0} + // 1 + // {1 2.5} + // 2.5 +} + +func Example_vmModules() { + + e := env.NewEnv() + + err := e.Define("println", fmt.Println) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + + script := ` +module rectangle { + _length = 1 + _width = 1 + + func setLength (length) { + if length <= 0 { + return + } + _length = length + } + + func setWidth (width) { + if width <= 0 { + return + } + _width = width + } + + func area () { + return _length * _width + } + + func perimeter () { + return 2 * (_length + _width) + } + } + +rectangle1 = rectangle + +rectangle1.setLength(4) +rectangle1.setWidth(5) + +println(rectangle1.area()) +println(rectangle1.perimeter()) + +rectangle2 = rectangle + +rectangle2.setLength(2) +rectangle2.setWidth(4) + +println(rectangle2.area()) +println(rectangle2.perimeter()) +` + + _, err = vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("execute error: %v\n", err) + } + + // output: + // 20 + // 18 + // 8 + // 12 +} diff --git a/src/tool/run/vm/example_functions_test.go b/src/tool/run/vm/example_functions_test.go new file mode 100644 index 0000000..82b10d6 --- /dev/null +++ b/src/tool/run/vm/example_functions_test.go @@ -0,0 +1,184 @@ +package vm_test + +import ( + "fmt" + "log" + + "github.com/surdeus/goblin/src/tool/run/env" + "github.com/surdeus/goblin/src/tool/run/vm" +) + +func Example_vmFunctions() { + e := env.NewEnv() + + err := e.Define("println", fmt.Println) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + + script := ` +func a(b) { + println(b) +} +a("b") + +a = func(b) { + println(b) +} +a("b") + +func(b) { + println(b) +}("b") + +func a() { + return "a" +} +println(a()) + +println("") + + +func fib(n) { + if (n <= 1) { + return n + } + return fib(n - 1) + fib(n - 2) +} + +println(fib(8)) + + func sum(n...) { + t = 0 + for a in n { + t += a + } + return t + } +println(sum(1, 2, 3, 4)) + +func add(a, b) { + return a + b +} +println(add([1, 2]...)) +` + + _, err = vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("execute error: %v\n", err) + } + + // output: + // b + // b + // b + // a + // + // 21 + // 10 + // 3 + +} + +func Example_vmFunctionsScope() { + e := env.NewEnv() + + err := e.Define("println", fmt.Println) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + + script := ` +a = 1 +func () { + a = 2 +}() +println(a) + +var a = 1 +func () { + a = 2 +}() +println(a) + +a = 1 +func () { + var a = 2 +}() +println(a) + +var a = 1 +func () { + var a = 2 +}() +println(a) +` + + _, err = vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("execute error: %v\n", err) + } + + // output: + // 2 + // 2 + // 1 + // 1 + +} + +func testFunc1(a interface{}) int { + b, ok := a.([]interface{}) + if ok { + return len(b) + } + return 0 +} + +func Example_vmFunctionsOutside() { + + /* + // the following function would be uncommented + func testFunc1(a interface{}) int { + b, ok := a.([]interface{}) + if ok { + return len(b) + } + return 0 + } + */ + + e := env.NewEnv() + + err := e.Define("println", fmt.Println) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + err = e.Define("addString", func(a string, b string) string { return a + b }) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + // uses the function that would be declared above + err = e.Define("aFunc", testFunc1) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + + script := ` +a = addString("a", "b") +println(a) + +a = aFunc([1, 2, 3]) +println(a) +` + + _, err = vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("execute error: %v\n", err) + } + + // output: + // ab + // 3 + +} diff --git a/src/tool/run/vm/example_operators_test.go b/src/tool/run/vm/example_operators_test.go new file mode 100644 index 0000000..a6d8f27 --- /dev/null +++ b/src/tool/run/vm/example_operators_test.go @@ -0,0 +1,385 @@ +package vm_test + +import ( + "fmt" + "log" + + "github.com/surdeus/goblin/src/tool/run/env" + "github.com/surdeus/goblin/src/tool/run/vm" +) + +func Example_vmBasicOperators() { + e := env.NewEnv() + + err := e.Define("println", fmt.Println) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + + script := ` +a = nil +println(a) +a = true +println(a) + +println("") + +a = 2 + 1 +println(a) +a = 2 - 1 +println(a) +a = 2 * 1 +println(a) +a = 4 / 2 +println(a) + +println("") + +a = 1 +a++ +println(a) +a-- +println(a) + +println("") + +a = 1 +a += 1 +println(a) +a -= 1 +println(a) +a *= 4 +println(a) +a /= 2 +println(a) + +println("") + +a = 1 & 3 +println(a) +a = 1 | 2 +println(a) + +println("") + +a = 2 << 3 +println(a) +a = 8 >> 2 +println(a) +a = 7 % 3 +println(a) + +println("") + +a = 2 - (-2) +println(a) +a = ^2 +println(a) +a = "a" * 4 +println(a) +a = !true +println(a) + +` + + _, err = vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("execute error: %v\n", err) + } + + // output: + // + // true + // + // 3 + // 1 + // 2 + // 2 + // + // 2 + // 1 + // + // 2 + // 1 + // 4 + // 2 + // + // 1 + // 3 + // + // 16 + // 2 + // 1 + // + // 4 + // -3 + // aaaa + // false + +} + +func Example_vmComparisonOperators() { + e := env.NewEnv() + + err := e.Define("println", fmt.Println) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + + script := ` +a = nil == nil +println(a) +a = "a" != "a" +println(a) +a = 1 == 1.0 +println(a) +a = !true +println(a) + +println("") + +a = 1 < 2 +println(a) +a = 1 > 3 +println(a) +a = 2 <= 2 +println(a) +a = 2 >= 3 +println(a) + +println("") +a = 1 == 2 && 1 == 1 +println(a) +a = 1 == 2 || 1 == 1 +println(a) +` + + _, err = vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("execute error: %v\n", err) + } + + // output: + // true + // false + // true + // false + // + // true + // false + // true + // false + // + // false + // true + +} + +func Example_vmIfOperators() { + e := env.NewEnv() + + err := e.Define("println", fmt.Println) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + + script := ` +a = 1 +b = 2 + +if a == 1 { + println(a) +} + +if b == 1 { + println(a) +} else { + println(b) +} + +if a == 3 { + println(a) +} else if b == 3 { + println(b) +} else { + println(a + b) +} + +println("") + +if a == 2 || b == 2 { + println(4) +} + +if a == 1 && b == 2 { + println(5) +} +` + + _, err = vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("execute error: %v\n", err) + } + + // output: + // 1 + // 2 + // 3 + // + // 4 + // 5 + +} + +func Example_vmForLoops() { + e := env.NewEnv() + + err := e.Define("println", fmt.Println) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + + script := ` +i = 0 +for { + println(i) + i++ + if i > 1 { + break + } +} + +println("") + +for i in [0, 1] { + println(i) +} + +println("") + +for key, value in {"a": "b"} { + println(key, value) +} + +println("") + +i = 0 +for i < 2 { + println(i) + i++ +} + +println("") + +for i = 0; i < 2; i++ { + println(i) +} + +println("") + + +for i = 0; i < 10; i++ { + println(i) + if i < 1 { + continue + } + break +} + +` + + _, err = vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("execute error: %v\n", err) + } + + // output: + // 0 + // 1 + // + // 0 + // 1 + // + // a b + // + // 0 + // 1 + // + // 0 + // 1 + // + // 0 + // 1 + +} + +func Example_vmSlices() { + e := env.NewEnv() + + err := e.Define("println", fmt.Println) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + + script := ` +a = "abc" +println(a[1:]) +println(a[:2]) +println(a[1:2]) + +println("") + +a = [1, 2, 3] +println(a[1:]) +println(a[:2]) +println(a[1:2]) +` + + _, err = vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("execute error: %v\n", err) + } + + // output: + // bc + // ab + // b + // + // [2 3] + // [1 2] + // [2] + +} + +func Example_vmChannels() { + e := env.NewEnv() + + err := e.Define("println", fmt.Println) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + + script := ` +a = make(chan string, 1) +a <- "a" +println(<- a) + +a = make(chan string) +go func() { + a <- "a" +}() +println(<- a) + + +` + + _, err = vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("execute error: %v\n", err) + } + + // output: + // a + // a + +} diff --git a/src/tool/run/vm/example_packages_test.go b/src/tool/run/vm/example_packages_test.go new file mode 100644 index 0000000..ce7e158 --- /dev/null +++ b/src/tool/run/vm/example_packages_test.go @@ -0,0 +1,131 @@ +package vm_test + +import ( + "log" + + "github.com/surdeus/goblin/src/tool/run/env" + _ "github.com/surdeus/goblin/src/tool/run/packages" + "github.com/surdeus/goblin/src/tool/run/vm" +) + +func Example_vmSort() { + // _ "github.com/surdeus/goblin/src/tool/run/packages" + + e := env.NewEnv() + + script := ` +fmt = import("fmt") +sort = import("sort") +a = [5, 1.1, 3, "f", "2", "4.4"] +sortFuncs = make(sort.SortFuncsStruct) +sortFuncs.LenFunc = func() { return len(a) } +sortFuncs.LessFunc = func(i, j) { return a[i] < a[j] } +sortFuncs.SwapFunc = func(i, j) { temp = a[i]; a[i] = a[j]; a[j] = temp } +sort.Sort(sortFuncs) +fmt.Println(a) +` + + _, err := vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("execute error: %v\n", err) + } + + // output: + // [f 1.1 2 3 4.4 5] +} + +func Example_vmRegexp() { + // _ "github.com/surdeus/goblin/src/tool/run/packages" + + e := env.NewEnv() + + script := ` +fmt = import("fmt") +regexp = import("regexp") + +re = regexp.MustCompile("^simple$") +result = re.MatchString("simple") +fmt.Println(result) +fmt.Println("") + +re = regexp.MustCompile("simple") +result = re.FindString("This is a simple sentence") +fmt.Println(result) +fmt.Println("") + +re = regexp.MustCompile(",") +result = re.Split("a,b,c", -1) +fmt.Println(result) +fmt.Println("") + +re = regexp.MustCompile("foo") +result = re.ReplaceAllString("foo", "bar") +fmt.Println(result) +` + + _, err := vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("execute error: %v\n", err) + } + + // output: + // true + // + // simple + // + // [a b c] + // + // bar +} + +func Example_vmHttp() { + // _ "github.com/surdeus/goblin/src/tool/run/packages" + + e := env.NewEnv() + + script := ` +fmt = import("fmt") +io = import("io") +ioutil = import("io/ioutil") +net = import("net") +http = import("net/http") +time = import("time") + +func handlerRoot(responseWriter, request) { + io.WriteString(responseWriter, "Hello World :)") +} + +serveMux = http.NewServeMux() +serveMux.HandleFunc("/", handlerRoot) +listener, err = net.Listen("tcp", ":8080") +if err != nil { + fmt.Println(err) + return +} +go http.Serve(listener, serveMux) + +client = http.DefaultClient + +response, err = client.Get("http://localhost:8080/") +if err != nil { + fmt.Println(err) + return +} + +body, err = ioutil.ReadAll(response.Body) +if err != nil { + fmt.Println(err) +} +response.Body.Close() + +fmt.Printf("%s\n", body) +` + + _, err := vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("execute error: %v\n", err) + } + + // output: + // Hello World :) +} diff --git a/src/tool/run/vm/example_test.go b/src/tool/run/vm/example_test.go new file mode 100644 index 0000000..7202b6b --- /dev/null +++ b/src/tool/run/vm/example_test.go @@ -0,0 +1,231 @@ +package vm_test + +import ( + "context" + "fmt" + "log" + "sync" + "time" + + "github.com/surdeus/goblin/src/tool/run/env" + "github.com/surdeus/goblin/src/tool/run/vm" +) + +func Example_vmExecuteContext() { + var waitGroup sync.WaitGroup + waitGroup.Add(1) + waitChan := make(chan struct{}, 1) + + e := env.NewEnv() + sleepMillisecond := func() { time.Sleep(time.Millisecond) } + + err := e.Define("println", fmt.Println) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + err = e.Define("sleep", sleepMillisecond) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + + script := ` +# sleep for 10 seconds +for i = 0; i < 10000; i++ { + sleep() +} +# the context should cancel before printing the next line +println("this line should not be printed") +` + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + close(waitChan) + v, err := vm.ExecuteContext(ctx, e, nil, script) + fmt.Println(v, err) + waitGroup.Done() + }() + + <-waitChan + cancel() + + waitGroup.Wait() + + // output: execution interrupted +} + +func Example_vmEnvDefine() { + // "github.com/surdeus/goblin/src/tool/run/env" + + e := env.NewEnv() + + err := e.Define("println", fmt.Println) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + + err = e.Define("a", true) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + err = e.Define("b", int64(1)) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + err = e.Define("c", float64(1.1)) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + err = e.Define("d", "d") + if err != nil { + log.Fatalf("define error: %v\n", err) + } + err = e.Define("e", []interface{}{true, int64(1), float64(1.1), "d"}) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + err = e.Define("f", map[string]interface{}{"a": true}) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + + script := ` +println(a) +println(b) +println(c) +println(d) +println(e) +println(f) +` + + _, err = vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("execute error: %v\n", err) + } + + // output: + // true + // 1 + // 1.1 + // d + // [true 1 1.1 d] + // map[a:true] +} + +func Example_vmEnv() { + // "github.com/surdeus/goblin/src/tool/run/env" + + e := env.NewEnv() + + err := e.Define("a", "a") + if err != nil { + log.Fatalf("define error: %v\n", err) + } + + _, err = e.Get("a") + if err != nil { + log.Fatalf("get error: %v\n", err) + } + + fmt.Println(e) + + // output: + // No parent + // a = "a" +} + +func Example_vmHelloWorld() { + // "github.com/surdeus/goblin/src/tool/run/env" + + e := env.NewEnv() + + err := e.Define("println", fmt.Println) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + + script := ` +println("Hello World :)") +` + + _, err = vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("execute error: %v\n", err) + } + + // output: Hello World :) +} + +func Example_vmQuickStart() { + // "github.com/surdeus/goblin/src/tool/run/env" + + e := env.NewEnv() + + err := e.Define("println", fmt.Println) + if err != nil { + log.Fatalf("define error: %v\n", err) + } + + script := ` +// declare variables +x = 1 +y = x + 1 + +// print using outside the script defined println function +println(x + y) // 3 + +// if else statement +if x < 1 || y < 1 { + println(x) +} else if x < 1 && y < 1 { + println(y) +} else { + println(x + y) +} + +// slice +a = []interface{1, 2, 3} +println(a) // [1 2 3] +println(a[0]) // 1 + +// map +a = map[interface]interface{"x": 1} +println(a) // map[x:1] +a.b = 2 +a["c"] = 3 +println(a["b"]) // 2 +println(a.c) // 3 + +// struct +a = make(struct { + A int64, + B float64 +}) +a.A = 4 +a.B = 5.5 +println(a.A) // 4 +println(a.B) // 5.5 + +// function +func a (x) { + println(x + 1) +} +a(5) // 6 +` + + _, err = vm.Execute(e, nil, script) + if err != nil { + log.Fatalf("execute error: %v\n", err) + } + + // output: + // 3 + // 3 + // [1 2 3] + // 1 + // map[x:1] + // 2 + // 3 + // 4 + // 5.5 + // 6 +} diff --git a/src/tool/run/vm/main_test.go b/src/tool/run/vm/main_test.go new file mode 100644 index 0000000..4aa145f --- /dev/null +++ b/src/tool/run/vm/main_test.go @@ -0,0 +1,268 @@ +package vm + +import ( + "context" + "reflect" + "testing" + "time" + + "github.com/surdeus/goblin/src/tool/run/env" + "github.com/surdeus/goblin/src/tool/run/parser" +) + +type ( + testStruct1 struct { + aInterface interface{} + aBool bool + aInt32 int32 + aInt64 int64 + aFloat32 float32 + aFloat64 float32 + aString string + aFunc func() + + aPtrInterface *interface{} + aPtrBool *bool + aPtrInt32 *int32 + aPtrInt64 *int64 + aPtrFloat32 *float32 + aPtrFloat64 *float32 + aPtrString *string + aPtrSliceInterface *[]interface{} + aPtrSliceBool *[]bool + aPtrSliceInt32 *[]int32 + aPtrSliceInt64 *[]int64 + aPtrSliceFloat32 *[]float32 + aPtrSliceFloat64 *[]float32 + aPtrSliceString *[]string + + aSliceInterface []interface{} + aSliceBool []bool + aSliceInt32 []int32 + aSliceInt64 []int64 + aSliceFloat32 []float32 + aSliceFloat64 []float32 + aSliceString []string + aSlicePtrInterface []*interface{} + aSlicePtrBool []*bool + aSlicePtrInt32 []*int32 + aSlicePtrInt64 []*int64 + aSlicePtrFloat32 []*float32 + aSlicePtrFloat64 []*float32 + aSlicePtrString []*string + + aMapInterface map[string]interface{} + aMapBool map[string]bool + aMapInt32 map[string]int32 + aMapInt64 map[string]int64 + aMapFloat32 map[string]float32 + aMapFloat64 map[string]float32 + aMapString map[string]string + aMapPtrInterface map[string]*interface{} + aMapPtrBool map[string]*bool + aMapPtrInt32 map[string]*int32 + aMapPtrInt64 map[string]*int64 + aMapPtrFloat32 map[string]*float32 + aMapPtrFloat64 map[string]*float32 + aMapPtrString map[string]*string + + aChanInterface chan interface{} + aChanBool chan bool + aChanInt32 chan int32 + aChanInt64 chan int64 + aChanFloat32 chan float32 + aChanFloat64 chan float32 + aChanString chan string + aChanPtrInterface chan *interface{} + aChanPtrBool chan *bool + aChanPtrInt32 chan *int32 + aChanPtrInt64 chan *int64 + aChanPtrFloat32 chan *float32 + aChanPtrFloat64 chan *float32 + aChanPtrString chan *string + + aPtrStruct *testStruct1 + } + testStruct2 struct { + aStruct testStruct1 + } +) + +var ( + testVarValue = reflect.Value{} + testVarValueP = &reflect.Value{} + testVarBool = true + testVarBoolP = &testVarBool + testVarInt32 = int32(1) + testVarInt32P = &testVarInt32 + testVarInt64 = int64(1) + testVarInt64P = &testVarInt64 + testVarFloat32 = float32(1) + testVarFloat32P = &testVarFloat32 + testVarFloat64 = float64(1) + testVarFloat64P = &testVarFloat64 + testVarString = "a" + testVarStringP = &testVarString + testVarFunc = func() int64 { return 1 } + testVarFuncP = &testVarFunc + + testVarValueBool = reflect.ValueOf(true) + testVarValueInt32 = reflect.ValueOf(int32(1)) + testVarValueInt64 = reflect.ValueOf(int64(1)) + testVarValueFloat32 = reflect.ValueOf(float32(1.1)) + testVarValueFloat64 = reflect.ValueOf(float64(1.1)) + testVarValueString = reflect.ValueOf("a") + + testSliceEmpty []interface{} + testSlice = []interface{}{nil, true, int64(1), float64(1.1), "a"} + testMapEmpty map[interface{}]interface{} + testMap = map[interface{}]interface{}{"a": nil, "b": true, "c": int64(1), "d": float64(1.1), "e": "e"} +) + +// Test is utility struct to make tests easy. +type Test struct { + Script string + ParseError error + ParseErrorFunc *func(*testing.T, error) + EnvSetupFunc *func(*testing.T, *env.Env) + Types map[string]interface{} + Input map[string]interface{} + RunError error + RunErrorFunc *func(*testing.T, error) + RunOutput interface{} + Output map[string]interface{} +} + +// TestOptions is utility struct to pass options to the test. +type TestOptions struct { + EnvSetupFunc *func(*testing.T, *env.Env) + Timeout time.Duration +} + +// runTests runs VM tests +func runTests(t *testing.T, tests []Test, testOptions *TestOptions, options *Options) { + for _, test := range tests { + runTest(t, test, testOptions, options) + } +} + +// runTest runs VM test +func runTest(t *testing.T, test Test, testOptions *TestOptions, options *Options) { + timeout := 60 * time.Second + + // parser.EnableErrorVerbose() + // parser.EnableDebug(8) + + stmt, err := parser.ParseSrc(test.Script) + if test.ParseErrorFunc != nil { + (*test.ParseErrorFunc)(t, err) + } else if err != nil && test.ParseError != nil { + if err.Error() != test.ParseError.Error() { + t.Errorf("ParseSrc error - received: %v - expected: %v - script: %v", err, test.ParseError, test.Script) + return + } + } else if err != test.ParseError { + t.Errorf("ParseSrc error - received: %v - expected: %v - script: %v", err, test.ParseError, test.Script) + return + } + // Note: Still want to run the code even after a parse error to see what happens + + envTest := env.NewEnv() + if testOptions != nil { + if testOptions.EnvSetupFunc != nil { + (*testOptions.EnvSetupFunc)(t, envTest) + } + if testOptions.Timeout != 0 { + timeout = testOptions.Timeout + } + } + if test.EnvSetupFunc != nil { + (*test.EnvSetupFunc)(t, envTest) + } + + for typeName, typeValue := range test.Types { + err = envTest.DefineType(typeName, typeValue) + if err != nil { + t.Errorf("DefineType error: %v - typeName: %v - script: %v", err, typeName, test.Script) + return + } + } + + for inputName, inputValue := range test.Input { + err = envTest.Define(inputName, inputValue) + if err != nil { + t.Errorf("Define error: %v - inputName: %v - script: %v", err, inputName, test.Script) + return + } + } + + var value interface{} + ctx, cancel := context.WithTimeout(context.Background(), timeout) + value, err = RunContext(ctx, envTest, options, stmt) + cancel() + if test.RunErrorFunc != nil { + (*test.RunErrorFunc)(t, err) + } else if err != nil && test.RunError != nil { + if err.Error() != test.RunError.Error() { + t.Errorf("Run error - received: %v - expected: %v - script: %v", err, test.RunError, test.Script) + return + } + } else if err != test.RunError { + t.Errorf("Run error - received: %v - expected: %v - script: %v", err, test.RunError, test.Script) + return + } + + if !valueEqual(value, test.RunOutput) { + t.Errorf("Run output - received: %#v - expected: %#v - script: %v", value, test.RunOutput, test.Script) + t.Errorf("received type: %T - expected: %T", value, test.RunOutput) + return + } + + for outputName, outputValue := range test.Output { + value, err = envTest.Get(outputName) + if err != nil { + t.Errorf("Get error: %v - outputName: %v - script: %v", err, outputName, test.Script) + return + } + + if !valueEqual(value, outputValue) { + t.Errorf("outputName %v - received: %#v - expected: %#v - script: %v", outputName, value, outputValue, test.Script) + t.Errorf("received type: %T - expected: %T", value, outputValue) + continue + } + } +} + +// valueEqual return true if v1 and v2 is same value. If passed function, does +// extra checks otherwise just doing reflect.DeepEqual +func valueEqual(v1 interface{}, v2 interface{}) bool { + v1RV := reflect.ValueOf(v1) + switch v1RV.Kind() { + case reflect.Func: + // This is best effort to check if functions match, but it could be wrong + v2RV := reflect.ValueOf(v2) + if !v1RV.IsValid() || !v2RV.IsValid() { + if v1RV.IsValid() != !v2RV.IsValid() { + return false + } + return true + } else if v1RV.Kind() != v2RV.Kind() { + return false + } else if v1RV.Type() != v2RV.Type() { + return false + } else if v1RV.Pointer() != v2RV.Pointer() { + // From reflect: If v's Kind is Func, the returned pointer is an underlying code pointer, but not necessarily enough to identify a single function uniquely. + return false + } + return true + } + switch value1 := v1.(type) { + case error: + switch value2 := v2.(type) { + case error: + return value1.Error() == value2.Error() + } + } + + return reflect.DeepEqual(v1, v2) +} diff --git a/src/tool/run/vm/packagesGo110_test.go b/src/tool/run/vm/packagesGo110_test.go new file mode 100644 index 0000000..4b05743 --- /dev/null +++ b/src/tool/run/vm/packagesGo110_test.go @@ -0,0 +1,18 @@ +// +build go1.10 + +package vm + +import ( + "testing" + + _ "github.com/surdeus/goblin/src/tool/run/packages" +) + +func TestPackagesStringsGo110(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `strings = import("strings"); a = make(strings.Builder); _, err = a.WriteString("a"); if err != nil { return err.Error() }; _, err = a.WriteString("b"); if err != nil { return err.Error() }; _, err = a.WriteString("c"); if err != nil { return err.Error() }; a.String()`, RunOutput: "abc"}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} diff --git a/src/tool/run/vm/packages_test.go b/src/tool/run/vm/packages_test.go new file mode 100644 index 0000000..a13affe --- /dev/null +++ b/src/tool/run/vm/packages_test.go @@ -0,0 +1,209 @@ +package vm + +import ( + "fmt" + "reflect" + "testing" + + "github.com/surdeus/goblin/src/tool/run/env" + _ "github.com/surdeus/goblin/src/tool/run/packages" +) + +func TestImport(t *testing.T) { + tests := []Test{ + {Script: `a = import(1++)`, RunError: fmt.Errorf("invalid operation")}, + {Script: `a = import(true)`, RunError: fmt.Errorf("invalid type conversion")}, + {Script: `a = import("foo")`, RunError: fmt.Errorf("package not found: foo")}, + } + runTests(t, tests, nil, &Options{Debug: true}) + + envPackages := env.Packages + envPackageTypes := env.PackageTypes + + env.Packages = map[string]map[string]reflect.Value{"testPackage": {"a.b": reflect.ValueOf(1)}} + tests = []Test{ + {Script: `a = import("testPackage")`, RunError: fmt.Errorf("import DefineValue error: symbol contains '.'")}, + } + runTests(t, tests, nil, &Options{Debug: true}) + + env.Packages = map[string]map[string]reflect.Value{"testPackage": {"a": reflect.ValueOf(1)}} + env.PackageTypes = map[string]map[string]reflect.Type{"testPackage": {"a.b": reflect.TypeOf(1)}} + tests = []Test{ + {Script: `a = import("testPackage")`, RunError: fmt.Errorf("import DefineReflectType error: symbol contains '.'")}, + } + runTests(t, tests, nil, &Options{Debug: true}) + + env.PackageTypes = envPackageTypes + env.Packages = envPackages +} + +func TestPackagesBytes(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `bytes = import("bytes"); a = make(bytes.Buffer); n, err = a.WriteString("a"); if err != nil { return err }; n`, RunOutput: 1}, + {Script: `bytes = import("bytes"); a = make(bytes.Buffer); n, err = a.WriteString("a"); if err != nil { return err }; a.String()`, RunOutput: "a"}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestPackagesJson(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `json = import("encoding/json"); a = make(map[string]interface); a["b"] = "b"; c, err = json.Marshal(a); err`, Output: map[string]interface{}{"a": map[string]interface{}{"b": "b"}, "c": []byte(`{"b":"b"}`)}}, + {Script: `json = import("encoding/json"); b = 1; err = json.Unmarshal(a, &b); err`, Input: map[string]interface{}{"a": []byte(`{"b": "b"}`)}, Output: map[string]interface{}{"a": []byte(`{"b": "b"}`), "b": map[string]interface{}{"b": "b"}}}, + {Script: `json = import("encoding/json"); b = 1; err = json.Unmarshal(a, &b); err`, Input: map[string]interface{}{"a": `{"b": "b"}`}, Output: map[string]interface{}{"a": `{"b": "b"}`, "b": map[string]interface{}{"b": "b"}}}, + {Script: `json = import("encoding/json"); b = 1; err = json.Unmarshal(a, &b); err`, Input: map[string]interface{}{"a": `[["1", "2"],["3", "4"]]`}, Output: map[string]interface{}{"a": `[["1", "2"],["3", "4"]]`, "b": []interface{}{[]interface{}{"1", "2"}, []interface{}{"3", "4"}}}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestPackagesRegexp(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `regexp = import("regexp"); re = regexp.MustCompile("^simple$"); re.MatchString("simple")`, RunOutput: true}, + {Script: `regexp = import("regexp"); re = regexp.MustCompile("^simple$"); re.MatchString("no match")`, RunOutput: false}, + + {Script: `regexp = import("regexp"); re = regexp.MustCompile(a); re.MatchString(b)`, Input: map[string]interface{}{"a": "^simple$", "b": "simple"}, RunOutput: true, Output: map[string]interface{}{"a": "^simple$", "b": "simple"}}, + {Script: `regexp = import("regexp"); re = regexp.MustCompile(a); re.MatchString(b)`, Input: map[string]interface{}{"a": "^simple$", "b": "no match"}, RunOutput: false, Output: map[string]interface{}{"a": "^simple$", "b": "no match"}}, + + {Script: `regexp = import("regexp"); re = regexp.MustCompile("^a\\.\\d+\\.b$"); re.String()`, RunOutput: "^a\\.\\d+\\.b$"}, + {Script: `regexp = import("regexp"); re = regexp.MustCompile("^a\\.\\d+\\.b$"); re.MatchString("a.1.b")`, RunOutput: true}, + {Script: `regexp = import("regexp"); re = regexp.MustCompile("^a\\.\\d+\\.b$"); re.MatchString("a.22.b")`, RunOutput: true}, + {Script: `regexp = import("regexp"); re = regexp.MustCompile("^a\\.\\d+\\.b$"); re.MatchString("a.333.b")`, RunOutput: true}, + {Script: `regexp = import("regexp"); re = regexp.MustCompile("^a\\.\\d+\\.b$"); re.MatchString("no match")`, RunOutput: false}, + {Script: `regexp = import("regexp"); re = regexp.MustCompile("^a\\.\\d+\\.b$"); re.MatchString("a+1+b")`, RunOutput: false}, + + {Script: `regexp = import("regexp"); re = regexp.MustCompile(a); re.String()`, Input: map[string]interface{}{"a": "^a\\.\\d+\\.b$"}, RunOutput: "^a\\.\\d+\\.b$", Output: map[string]interface{}{"a": "^a\\.\\d+\\.b$"}}, + {Script: `regexp = import("regexp"); re = regexp.MustCompile(a); re.MatchString(b)`, Input: map[string]interface{}{"a": "^a\\.\\d+\\.b$", "b": "a.1.b"}, RunOutput: true, Output: map[string]interface{}{"a": "^a\\.\\d+\\.b$", "b": "a.1.b"}}, + {Script: `regexp = import("regexp"); re = regexp.MustCompile(a); re.MatchString(b)`, Input: map[string]interface{}{"a": "^a\\.\\d+\\.b$", "b": "a.22.b"}, RunOutput: true, Output: map[string]interface{}{"a": "^a\\.\\d+\\.b$", "b": "a.22.b"}}, + {Script: `regexp = import("regexp"); re = regexp.MustCompile(a); re.MatchString(b)`, Input: map[string]interface{}{"a": "^a\\.\\d+\\.b$", "b": "a.333.b"}, RunOutput: true, Output: map[string]interface{}{"a": "^a\\.\\d+\\.b$", "b": "a.333.b"}}, + {Script: `regexp = import("regexp"); re = regexp.MustCompile(a); re.MatchString(b)`, Input: map[string]interface{}{"a": "^a\\.\\d+\\.b$", "b": "no match"}, RunOutput: false, Output: map[string]interface{}{"a": "^a\\.\\d+\\.b$", "b": "no match"}}, + {Script: `regexp = import("regexp"); re = regexp.MustCompile(a); re.MatchString(b)`, Input: map[string]interface{}{"a": "^a\\.\\d+\\.b$", "b": "a+1+b"}, RunOutput: false, Output: map[string]interface{}{"a": "^a\\.\\d+\\.b$", "b": "a+1+b"}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestPackagesSort(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `sort = import("sort"); a = make([]int); a += [5, 3, 1, 4, 2]; sort.Ints(a); a`, RunOutput: []int{1, 2, 3, 4, 5}, Output: map[string]interface{}{"a": []int{1, 2, 3, 4, 5}}}, + {Script: `sort = import("sort"); a = make([]float64); a += [5.5, 3.3, 1.1, 4.4, 2.2]; sort.Float64s(a); a`, RunOutput: []float64{1.1, 2.2, 3.3, 4.4, 5.5}, Output: map[string]interface{}{"a": []float64{1.1, 2.2, 3.3, 4.4, 5.5}}}, + {Script: `sort = import("sort"); a = make([]string); a += ["e", "c", "a", "d", "b"]; sort.Strings(a); a`, RunOutput: []string{"a", "b", "c", "d", "e"}, Output: map[string]interface{}{"a": []string{"a", "b", "c", "d", "e"}}}, + {Script: ` +sort = import("sort") +a = [5, 1.1, 3, "f", "2", "4.4"] +sortFuncs = make(sort.SortFuncsStruct) +sortFuncs.LenFunc = func() { return len(a) } +sortFuncs.LessFunc = func(i, j) { return a[i] < a[j] } +sortFuncs.SwapFunc = func(i, j) { temp = a[i]; a[i] = a[j]; a[j] = temp } +sort.Sort(sortFuncs) +a +`, + RunOutput: []interface{}{"f", float64(1.1), "2", int64(3), "4.4", int64(5)}, Output: map[string]interface{}{"a": []interface{}{"f", float64(1.1), "2", int64(3), "4.4", int64(5)}}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestPackagesStrconv(t *testing.T) { + t.Parallel() + + var toRune = func(s string) rune { + if len(s) == 0 { + return 0 + } + return []rune(s)[0] + } + var toString = func(v interface{}) string { + if b, ok := v.([]byte); ok { + return string(b) + } + return fmt.Sprint(v) + } + tests := []Test{ + {Script: `strconv = import("strconv"); a = true; b = strconv.FormatBool(a)`, RunOutput: "true", Output: map[string]interface{}{"a": true, "b": "true"}}, + {Script: `strconv = import("strconv"); a = 1.1; b = strconv.FormatFloat(a, toRune("f"), -1, 64)`, Input: map[string]interface{}{"toRune": toRune}, RunOutput: "1.1", Output: map[string]interface{}{"a": float64(1.1), "b": "1.1"}}, + {Script: `strconv = import("strconv"); a = 1; b = strconv.FormatInt(a, 10)`, RunOutput: "1", Output: map[string]interface{}{"a": int64(1), "b": "1"}}, + {Script: `strconv = import("strconv"); b = strconv.FormatInt(a, 10)`, Input: map[string]interface{}{"a": uint64(1)}, RunOutput: "1", Output: map[string]interface{}{"a": uint64(1), "b": "1"}}, + + {Script: `strconv = import("strconv"); a = "true"; b, err = strconv.ParseBool(a); err = toString(err)`, Input: map[string]interface{}{"toString": toString}, RunOutput: "", Output: map[string]interface{}{"a": "true", "b": true, "err": ""}}, + {Script: `strconv = import("strconv"); a = "2"; b, err = strconv.ParseBool(a); err = toString(err)`, Input: map[string]interface{}{"toString": toString}, RunOutput: `strconv.ParseBool: parsing "2": invalid syntax`, Output: map[string]interface{}{"a": "2", "b": false, "err": `strconv.ParseBool: parsing "2": invalid syntax`}}, + {Script: `strconv = import("strconv"); a = "1.1"; b, err = strconv.ParseFloat(a, 64); err = toString(err)`, Input: map[string]interface{}{"toString": toString}, RunOutput: "", Output: map[string]interface{}{"a": "1.1", "b": float64(1.1), "err": ""}}, + {Script: `strconv = import("strconv"); a = "a"; b, err = strconv.ParseFloat(a, 64); err = toString(err)`, Input: map[string]interface{}{"toString": toString}, RunOutput: `strconv.ParseFloat: parsing "a": invalid syntax`, Output: map[string]interface{}{"a": "a", "b": float64(0), "err": `strconv.ParseFloat: parsing "a": invalid syntax`}}, + {Script: `strconv = import("strconv"); a = "1"; b, err = strconv.ParseInt(a, 10, 64); err = toString(err)`, Input: map[string]interface{}{"toString": toString}, RunOutput: "", Output: map[string]interface{}{"a": "1", "b": int64(1), "err": ""}}, + {Script: `strconv = import("strconv"); a = "1.1"; b, err = strconv.ParseInt(a, 10, 64); err = toString(err)`, Input: map[string]interface{}{"toString": toString}, RunOutput: `strconv.ParseInt: parsing "1.1": invalid syntax`, Output: map[string]interface{}{"a": "1.1", "b": int64(0), "err": `strconv.ParseInt: parsing "1.1": invalid syntax`}}, + {Script: `strconv = import("strconv"); a = "a"; b, err = strconv.ParseInt(a, 10, 64); err = toString(err)`, Input: map[string]interface{}{"toString": toString}, RunOutput: `strconv.ParseInt: parsing "a": invalid syntax`, Output: map[string]interface{}{"a": "a", "b": int64(0), "err": `strconv.ParseInt: parsing "a": invalid syntax`}}, + {Script: `strconv = import("strconv"); a = "1"; b, err = strconv.ParseUint(a, 10, 64); err = toString(err)`, Input: map[string]interface{}{"toString": toString}, RunOutput: "", Output: map[string]interface{}{"a": "1", "b": uint64(1), "err": ""}}, + {Script: `strconv = import("strconv"); a = "a"; b, err = strconv.ParseUint(a, 10, 64); err = toString(err)`, Input: map[string]interface{}{"toString": toString}, RunOutput: `strconv.ParseUint: parsing "a": invalid syntax`, Output: map[string]interface{}{"a": "a", "b": uint64(0), "err": `strconv.ParseUint: parsing "a": invalid syntax`}}, + + {Script: `strconv = import("strconv"); a = "true"; var b, err = strconv.ParseBool(a); err = toString(err)`, Input: map[string]interface{}{"toString": toString}, RunOutput: "", Output: map[string]interface{}{"a": "true", "b": true, "err": ""}}, + {Script: `strconv = import("strconv"); a = "2"; var b, err = strconv.ParseBool(a); err = toString(err)`, Input: map[string]interface{}{"toString": toString}, RunOutput: `strconv.ParseBool: parsing "2": invalid syntax`, Output: map[string]interface{}{"a": "2", "b": false, "err": `strconv.ParseBool: parsing "2": invalid syntax`}}, + {Script: `strconv = import("strconv"); a = "1.1"; var b, err = strconv.ParseFloat(a, 64); err = toString(err)`, Input: map[string]interface{}{"toString": toString}, RunOutput: "", Output: map[string]interface{}{"a": "1.1", "b": float64(1.1), "err": ""}}, + {Script: `strconv = import("strconv"); a = "a"; var b, err = strconv.ParseFloat(a, 64); err = toString(err)`, Input: map[string]interface{}{"toString": toString}, RunOutput: `strconv.ParseFloat: parsing "a": invalid syntax`, Output: map[string]interface{}{"a": "a", "b": float64(0), "err": `strconv.ParseFloat: parsing "a": invalid syntax`}}, + {Script: `strconv = import("strconv"); a = "1"; var b, err = strconv.ParseInt(a, 10, 64); err = toString(err)`, Input: map[string]interface{}{"toString": toString}, RunOutput: "", Output: map[string]interface{}{"a": "1", "b": int64(1), "err": ""}}, + {Script: `strconv = import("strconv"); a = "1.1"; var b, err = strconv.ParseInt(a, 10, 64); err = toString(err)`, Input: map[string]interface{}{"toString": toString}, RunOutput: `strconv.ParseInt: parsing "1.1": invalid syntax`, Output: map[string]interface{}{"a": "1.1", "b": int64(0), "err": `strconv.ParseInt: parsing "1.1": invalid syntax`}}, + {Script: `strconv = import("strconv"); a = "a"; var b, err = strconv.ParseInt(a, 10, 64); err = toString(err)`, Input: map[string]interface{}{"toString": toString}, RunOutput: `strconv.ParseInt: parsing "a": invalid syntax`, Output: map[string]interface{}{"a": "a", "b": int64(0), "err": `strconv.ParseInt: parsing "a": invalid syntax`}}, + {Script: `strconv = import("strconv"); a = "1"; var b, err = strconv.ParseUint(a, 10, 64); err = toString(err)`, Input: map[string]interface{}{"toString": toString}, RunOutput: "", Output: map[string]interface{}{"a": "1", "b": uint64(1), "err": ""}}, + {Script: `strconv = import("strconv"); a = "a"; var b, err = strconv.ParseUint(a, 10, 64); err = toString(err)`, Input: map[string]interface{}{"toString": toString}, RunOutput: `strconv.ParseUint: parsing "a": invalid syntax`, Output: map[string]interface{}{"a": "a", "b": uint64(0), "err": `strconv.ParseUint: parsing "a": invalid syntax`}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestPackagesStrings(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `strings = import("strings"); a = " one two "; b = strings.TrimSpace(a)`, RunOutput: "one two", Output: map[string]interface{}{"a": " one two ", "b": "one two"}}, + {Script: `strings = import("strings"); a = "a b c d"; b = strings.Split(a, " ")`, RunOutput: []string{"a", "b", "c", "d"}, Output: map[string]interface{}{"a": "a b c d", "b": []string{"a", "b", "c", "d"}}}, + {Script: `strings = import("strings"); a = "a b c d"; b = strings.SplitN(a, " ", 3)`, RunOutput: []string{"a", "b", "c d"}, Output: map[string]interface{}{"a": "a b c d", "b": []string{"a", "b", "c d"}}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestPackagesSync(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `sync = import("sync"); once = make(sync.Once); a = []; func add() { a += "a" }; once.Do(add); once.Do(add); a`, RunOutput: []interface{}{"a"}, Output: map[string]interface{}{"a": []interface{}{"a"}}}, + {Script: `sync = import("sync"); waitGroup = make(sync.WaitGroup); waitGroup.Add(2); func done() { waitGroup.Done() }; go done(); go done(); waitGroup.Wait(); "a"`, RunOutput: "a"}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestPackagesTime(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `time = import("time"); a = make(time.Time); a.IsZero()`, RunOutput: true}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestPackagesURL(t *testing.T) { + t.Parallel() + + e := env.NewEnv() + value, err := Execute(e, nil, ` +url = import("net/url") +v1 = make(url.Values) +v1.Set("a", "a") +if v1.Get("a") != "a" { + return "value a not set" +} +v2 = make(url.Values) +v2.Set("b", "b") +if v2.Get("b") != "b" { + return "value b not set" +} +v2.Get("a") +`) + if err != nil { + t.Errorf("execute error - received: %v expected: %v", err, nil) + } + if value != "" { + t.Errorf("execute value - received: %#v expected: %#v", value, "") + } +} diff --git a/src/tool/run/vm/vm.go b/src/tool/run/vm/vm.go new file mode 100644 index 0000000..e7aadfd --- /dev/null +++ b/src/tool/run/vm/vm.go @@ -0,0 +1,458 @@ +package vm + +import ( + "context" + "errors" + "fmt" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/ast" + "github.com/surdeus/goblin/src/tool/run/env" +) + +// Options provides options to run VM with +type Options struct { + Debug bool // run in Debug mode +} + +type ( + // Error is a VM run error. + Error struct { + Message string + Pos ast.Position + } + + // runInfo provides run incoming and outgoing information + runInfoStruct struct { + // incoming + ctx context.Context + env *env.Env + options *Options + stmt ast.Stmt + expr ast.Expr + operator ast.Operator + + // outgoing + rv reflect.Value + err error + } +) + +var ( + nilType = reflect.TypeOf(nil) + stringType = reflect.TypeOf("a") + byteType = reflect.TypeOf(byte('a')) + runeType = reflect.TypeOf('a') + interfaceType = reflect.ValueOf([]interface{}{int64(1)}).Index(0).Type() + interfaceSliceType = reflect.TypeOf([]interface{}{}) + reflectValueType = reflect.TypeOf(reflect.Value{}) + errorType = reflect.ValueOf([]error{nil}).Index(0).Type() + vmErrorType = reflect.TypeOf(&Error{}) + contextType = reflect.TypeOf((*context.Context)(nil)).Elem() + + nilValue = reflect.New(reflect.TypeOf((*interface{})(nil)).Elem()).Elem() + trueValue = reflect.ValueOf(true) + falseValue = reflect.ValueOf(false) + zeroValue = reflect.Value{} + reflectValueNilValue = reflect.ValueOf(nilValue) + reflectValueErrorNilValue = reflect.ValueOf(reflect.New(errorType).Elem()) + + errInvalidTypeConversion = fmt.Errorf("invalid type conversion") + + // ErrBreak when there is an unexpected break statement + ErrBreak = errors.New("unexpected break statement") + // ErrContinue when there is an unexpected continue statement + ErrContinue = errors.New("unexpected continue statement") + // ErrReturn when there is an unexpected return statement + ErrReturn = errors.New("unexpected return statement") + // ErrInterrupt when execution has been interrupted + ErrInterrupt = errors.New("execution interrupted") +) + +// Error returns the VM error message. +func (e *Error) Error() string { + return e.Message +} + +// newError makes VM error from error +func newError(pos ast.Pos, err error) error { + if err == nil { + return nil + } + if pos == nil { + return &Error{Message: err.Error(), Pos: ast.Position{Line: 1, Column: 1}} + } + return &Error{Message: err.Error(), Pos: pos.Position()} +} + +// newStringError makes VM error from string +func newStringError(pos ast.Pos, err string) error { + if err == "" { + return nil + } + if pos == nil { + return &Error{Message: err, Pos: ast.Position{Line: 1, Column: 1}} + } + return &Error{Message: err, Pos: pos.Position()} +} + +// recoverFunc generic recover function +func recoverFunc(runInfo *runInfoStruct) { + recoverInterface := recover() + if recoverInterface == nil { + return + } + switch value := recoverInterface.(type) { + case *Error: + runInfo.err = value + case error: + runInfo.err = value + default: + runInfo.err = fmt.Errorf("%v", recoverInterface) + } +} + +func isNil(v reflect.Value) bool { + switch v.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + // from reflect IsNil: + // Note that IsNil is not always equivalent to a regular comparison with nil in Go. + // For example, if v was created by calling ValueOf with an uninitialized interface variable i, + // i==nil will be true but v.IsNil will panic as v will be the zero Value. + return v.IsNil() + default: + return false + } +} + +func isNum(v reflect.Value) bool { + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, + reflect.Float32, reflect.Float64: + return true + } + return false +} + +// equal returns true when lhsV and rhsV is same value. +func equal(lhsV, rhsV reflect.Value) bool { + lhsIsNil, rhsIsNil := isNil(lhsV), isNil(rhsV) + if lhsIsNil && rhsIsNil { + return true + } + if (!lhsIsNil && rhsIsNil) || (lhsIsNil && !rhsIsNil) { + return false + } + if lhsV.Kind() == reflect.Interface || lhsV.Kind() == reflect.Ptr { + lhsV = lhsV.Elem() + } + if rhsV.Kind() == reflect.Interface || rhsV.Kind() == reflect.Ptr { + rhsV = rhsV.Elem() + } + + // Compare a string and a number. + // This will attempt to convert the string to a number, + // while leaving the other side alone. Code further + // down takes care of converting ints and floats as needed. + if isNum(lhsV) && rhsV.Kind() == reflect.String { + rhsF, err := tryToFloat64(rhsV) + if err != nil { + // Couldn't convert RHS to a float, they can't be compared. + return false + } + rhsV = reflect.ValueOf(rhsF) + } else if lhsV.Kind() == reflect.String && isNum(rhsV) { + // If the LHS is a string formatted as an int, try that before trying float + lhsI, err := tryToInt64(lhsV) + if err != nil { + // if LHS is a float, e.g. "1.2", we need to set lhsV to a float64 + lhsF, err := tryToFloat64(lhsV) + if err != nil { + return false + } + lhsV = reflect.ValueOf(lhsF) + } else { + lhsV = reflect.ValueOf(lhsI) + } + } + + if isNum(lhsV) && isNum(rhsV) { + return fmt.Sprintf("%v", lhsV) == fmt.Sprintf("%v", rhsV) + } + + // Try to compare bools to strings and numbers + if lhsV.Kind() == reflect.Bool || rhsV.Kind() == reflect.Bool { + lhsB, err := tryToBool(lhsV) + if err != nil { + return false + } + rhsB, err := tryToBool(rhsV) + if err != nil { + return false + } + return lhsB == rhsB + } + + return reflect.DeepEqual(lhsV.Interface(), rhsV.Interface()) +} + +func getMapIndex(key reflect.Value, aMap reflect.Value) reflect.Value { + if aMap.IsNil() { + return nilValue + } + + var err error + key, err = convertReflectValueToType(key, aMap.Type().Key()) + if err != nil { + return nilValue + } + + // From reflect MapIndex: + // It returns the zero Value if key is not found in the map or if v represents a nil map. + value := aMap.MapIndex(key) + if !value.IsValid() { + return nilValue + } + + if aMap.Type().Elem() == interfaceType && !value.IsNil() { + value = reflect.ValueOf(value.Interface()) + } + + return value +} + +// appendSlice appends rhs to lhs +// function assumes lhsV and rhsV are slice or array +func appendSlice(expr ast.Expr, lhsV reflect.Value, rhsV reflect.Value) (reflect.Value, error) { + lhsT := lhsV.Type().Elem() + rhsT := rhsV.Type().Elem() + + if lhsT == rhsT { + return reflect.AppendSlice(lhsV, rhsV), nil + } + + if rhsT.ConvertibleTo(lhsT) { + for i := 0; i < rhsV.Len(); i++ { + lhsV = reflect.Append(lhsV, rhsV.Index(i).Convert(lhsT)) + } + return lhsV, nil + } + + leftHasSubArray := lhsT.Kind() == reflect.Slice || lhsT.Kind() == reflect.Array + rightHasSubArray := rhsT.Kind() == reflect.Slice || rhsT.Kind() == reflect.Array + + if leftHasSubArray != rightHasSubArray && lhsT != interfaceType && rhsT != interfaceType { + return nilValue, newStringError(expr, "invalid type conversion") + } + + if !leftHasSubArray && !rightHasSubArray { + for i := 0; i < rhsV.Len(); i++ { + value := rhsV.Index(i) + if rhsT == interfaceType { + value = value.Elem() + } + if lhsT == value.Type() { + lhsV = reflect.Append(lhsV, value) + } else if value.Type().ConvertibleTo(lhsT) { + lhsV = reflect.Append(lhsV, value.Convert(lhsT)) + } else { + return nilValue, newStringError(expr, "invalid type conversion") + } + } + return lhsV, nil + } + + if (leftHasSubArray || lhsT == interfaceType) && (rightHasSubArray || rhsT == interfaceType) { + for i := 0; i < rhsV.Len(); i++ { + value := rhsV.Index(i) + if rhsT == interfaceType { + value = value.Elem() + if value.Kind() != reflect.Slice && value.Kind() != reflect.Array { + return nilValue, newStringError(expr, "invalid type conversion") + } + } + newSlice, err := appendSlice(expr, reflect.MakeSlice(lhsT, 0, value.Len()), value) + if err != nil { + return nilValue, err + } + lhsV = reflect.Append(lhsV, newSlice) + } + return lhsV, nil + } + + return nilValue, newStringError(expr, "invalid type conversion") +} + +func makeType(runInfo *runInfoStruct, typeStruct *ast.TypeStruct) reflect.Type { + switch typeStruct.Kind { + case ast.TypeDefault: + return getTypeFromEnv(runInfo, typeStruct) + case ast.TypePtr: + var t reflect.Type + if typeStruct.SubType != nil { + t = makeType(runInfo, typeStruct.SubType) + } else { + t = getTypeFromEnv(runInfo, typeStruct) + } + if runInfo.err != nil { + return nil + } + if t == nil { + return nil + } + return reflect.PtrTo(t) + case ast.TypeSlice: + var t reflect.Type + if typeStruct.SubType != nil { + t = makeType(runInfo, typeStruct.SubType) + } else { + t = getTypeFromEnv(runInfo, typeStruct) + } + if runInfo.err != nil { + return nil + } + if t == nil { + return nil + } + for i := 1; i < typeStruct.Dimensions; i++ { + t = reflect.SliceOf(t) + } + return reflect.SliceOf(t) + case ast.TypeMap: + key := makeType(runInfo, typeStruct.Key) + if runInfo.err != nil { + return nil + } + if key == nil { + return nil + } + t := makeType(runInfo, typeStruct.SubType) + if runInfo.err != nil { + return nil + } + if t == nil { + return nil + } + if !runInfo.options.Debug { + // captures panic + defer recoverFunc(runInfo) + } + t = reflect.MapOf(key, t) + return t + case ast.TypeChan: + var t reflect.Type + if typeStruct.SubType != nil { + t = makeType(runInfo, typeStruct.SubType) + } else { + t = getTypeFromEnv(runInfo, typeStruct) + } + if runInfo.err != nil { + return nil + } + if t == nil { + return nil + } + return reflect.ChanOf(reflect.BothDir, t) + case ast.TypeStructType: + var t reflect.Type + fields := make([]reflect.StructField, 0, len(typeStruct.StructNames)) + for i := 0; i < len(typeStruct.StructNames); i++ { + t = makeType(runInfo, typeStruct.StructTypes[i]) + if runInfo.err != nil { + return nil + } + if t == nil { + return nil + } + fields = append(fields, reflect.StructField{Name: typeStruct.StructNames[i], Type: t}) + } + if !runInfo.options.Debug { + // captures panic + defer recoverFunc(runInfo) + } + t = reflect.StructOf(fields) + return t + default: + runInfo.err = fmt.Errorf("unknown kind") + return nil + } +} + +func getTypeFromEnv(runInfo *runInfoStruct, typeStruct *ast.TypeStruct) reflect.Type { + var e *env.Env + e, runInfo.err = runInfo.env.GetEnvFromPath(typeStruct.Env) + if runInfo.err != nil { + return nil + } + + var t reflect.Type + t, runInfo.err = e.Type(typeStruct.Name) + return t +} + +func makeValue(t reflect.Type) (reflect.Value, error) { + switch t.Kind() { + case reflect.Chan: + return reflect.MakeChan(t, 0), nil + case reflect.Func: + return reflect.MakeFunc(t, nil), nil + case reflect.Map: + // note creating slice as work around to create map + // just doing MakeMap can give incorrect type for defined types + value := reflect.MakeSlice(reflect.SliceOf(t), 0, 1) + value = reflect.Append(value, reflect.MakeMap(reflect.MapOf(t.Key(), t.Elem()))) + return value.Index(0), nil + case reflect.Ptr: + ptrV := reflect.New(t.Elem()) + v, err := makeValue(t.Elem()) + if err != nil { + return nilValue, err + } + + ptrV.Elem().Set(v) + return ptrV, nil + case reflect.Slice: + return reflect.MakeSlice(t, 0, 0), nil + case reflect.Struct: + structV := reflect.New(t).Elem() + for i := 0; i < structV.NumField(); i++ { + if structV.Field(i).Kind() == reflect.Ptr { + continue + } + v, err := makeValue(structV.Field(i).Type()) + if err != nil { + return nilValue, err + } + if structV.Field(i).CanSet() { + structV.Field(i).Set(v) + } + } + return structV, nil + } + return reflect.New(t).Elem(), nil +} + +// precedenceOfKinds returns the greater of two kinds +// string > float > int +func precedenceOfKinds(kind1 reflect.Kind, kind2 reflect.Kind) reflect.Kind { + if kind1 == kind2 { + return kind1 + } + switch kind1 { + case reflect.String: + return kind1 + case reflect.Float64, reflect.Float32: + switch kind2 { + case reflect.String: + return kind2 + } + return kind1 + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch kind2 { + case reflect.String, reflect.Float64, reflect.Float32: + return kind2 + } + } + return kind1 +} diff --git a/src/tool/run/vm/vmContainers_test.go b/src/tool/run/vm/vmContainers_test.go new file mode 100644 index 0000000..a829f1e --- /dev/null +++ b/src/tool/run/vm/vmContainers_test.go @@ -0,0 +1,1926 @@ +package vm + +import ( + "fmt" + "net/url" + "reflect" + "testing" + + "github.com/surdeus/goblin/src/tool/run/env" + "github.com/surdeus/goblin/src/tool/run/parser" +) + +func TestSlices(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `[1++]`, RunError: fmt.Errorf("invalid operation")}, + {Script: `1++[0]`, RunError: fmt.Errorf("invalid operation")}, + + {Script: `[]`, RunOutput: []interface{}{}}, + {Script: `[nil]`, RunOutput: []interface{}{nil}}, + {Script: `[true]`, RunOutput: []interface{}{true}}, + {Script: `["a"]`, RunOutput: []interface{}{"a"}}, + {Script: `[1]`, RunOutput: []interface{}{int64(1)}}, + {Script: `[1.1]`, RunOutput: []interface{}{float64(1.1)}}, + + {Script: `a = []; a.b`, RunError: fmt.Errorf("type slice does not support member operation")}, + {Script: `a = []; a.b = 1`, RunError: fmt.Errorf("type slice does not support member operation")}, + + {Script: `a = []`, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []interface{}{}}}, + {Script: `a = [nil]`, RunOutput: []interface{}{interface{}(nil)}, Output: map[string]interface{}{"a": []interface{}{interface{}(nil)}}}, + {Script: `a = [true]`, RunOutput: []interface{}{true}, Output: map[string]interface{}{"a": []interface{}{true}}}, + {Script: `a = [1]`, RunOutput: []interface{}{int64(1)}, Output: map[string]interface{}{"a": []interface{}{int64(1)}}}, + {Script: `a = [1.1]`, RunOutput: []interface{}{float64(1.1)}, Output: map[string]interface{}{"a": []interface{}{float64(1.1)}}}, + {Script: `a = ["a"]`, RunOutput: []interface{}{"a"}, Output: map[string]interface{}{"a": []interface{}{"a"}}}, + + {Script: `a = [[]]`, RunOutput: []interface{}{[]interface{}{}}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{}}}}, + {Script: `a = [[nil]]`, RunOutput: []interface{}{[]interface{}{interface{}(nil)}}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{interface{}(nil)}}}}, + {Script: `a = [[true]]`, RunOutput: []interface{}{[]interface{}{true}}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{true}}}}, + {Script: `a = [[1]]`, RunOutput: []interface{}{[]interface{}{int64(1)}}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1)}}}}, + {Script: `a = [[1.1]]`, RunOutput: []interface{}{[]interface{}{float64(1.1)}}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{float64(1.1)}}}}, + {Script: `a = [["a"]]`, RunOutput: []interface{}{[]interface{}{"a"}}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{"a"}}}}, + + {Script: `a = []; a += nil`, RunOutput: []interface{}{nil}, Output: map[string]interface{}{"a": []interface{}{nil}}}, + {Script: `a = []; a += true`, RunOutput: []interface{}{true}, Output: map[string]interface{}{"a": []interface{}{true}}}, + {Script: `a = []; a += 1`, RunOutput: []interface{}{int64(1)}, Output: map[string]interface{}{"a": []interface{}{int64(1)}}}, + {Script: `a = []; a += 1.1`, RunOutput: []interface{}{float64(1.1)}, Output: map[string]interface{}{"a": []interface{}{float64(1.1)}}}, + {Script: `a = []; a += "a"`, RunOutput: []interface{}{"a"}, Output: map[string]interface{}{"a": []interface{}{"a"}}}, + + {Script: `a = []; a += []`, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []interface{}{}}}, + {Script: `a = []; a += [nil]`, RunOutput: []interface{}{nil}, Output: map[string]interface{}{"a": []interface{}{nil}}}, + {Script: `a = []; a += [true]`, RunOutput: []interface{}{true}, Output: map[string]interface{}{"a": []interface{}{true}}}, + {Script: `a = []; a += [1]`, RunOutput: []interface{}{int64(1)}, Output: map[string]interface{}{"a": []interface{}{int64(1)}}}, + {Script: `a = []; a += [1.1]`, RunOutput: []interface{}{float64(1.1)}, Output: map[string]interface{}{"a": []interface{}{float64(1.1)}}}, + {Script: `a = []; a += ["a"]`, RunOutput: []interface{}{"a"}, Output: map[string]interface{}{"a": []interface{}{"a"}}}, + + {Script: `a = [0]; a[0]++`, RunOutput: int64(1), Output: map[string]interface{}{"a": []interface{}{int64(1)}}}, + {Script: `a = [[0]]; a[0][0]++`, RunOutput: int64(1), Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1)}}}}, + + {Script: `a = [2]; a[0]--`, RunOutput: int64(1), Output: map[string]interface{}{"a": []interface{}{int64(1)}}}, + {Script: `a = [[2]]; a[0][0]--`, RunOutput: int64(1), Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1)}}}}, + + {Script: `a`, Input: map[string]interface{}{"a": []bool{}}, RunOutput: []bool{}, Output: map[string]interface{}{"a": []bool{}}}, + {Script: `a`, Input: map[string]interface{}{"a": []int32{}}, RunOutput: []int32{}, Output: map[string]interface{}{"a": []int32{}}}, + {Script: `a`, Input: map[string]interface{}{"a": []int64{}}, RunOutput: []int64{}, Output: map[string]interface{}{"a": []int64{}}}, + {Script: `a`, Input: map[string]interface{}{"a": []float32{}}, RunOutput: []float32{}, Output: map[string]interface{}{"a": []float32{}}}, + {Script: `a`, Input: map[string]interface{}{"a": []float64{}}, RunOutput: []float64{}, Output: map[string]interface{}{"a": []float64{}}}, + {Script: `a`, Input: map[string]interface{}{"a": []string{}}, RunOutput: []string{}, Output: map[string]interface{}{"a": []string{}}}, + + {Script: `a`, Input: map[string]interface{}{"a": []bool{true, false}}, RunOutput: []bool{true, false}, Output: map[string]interface{}{"a": []bool{true, false}}}, + {Script: `a`, Input: map[string]interface{}{"a": []int32{1, 2}}, RunOutput: []int32{1, 2}, Output: map[string]interface{}{"a": []int32{1, 2}}}, + {Script: `a`, Input: map[string]interface{}{"a": []int64{1, 2}}, RunOutput: []int64{1, 2}, Output: map[string]interface{}{"a": []int64{1, 2}}}, + {Script: `a`, Input: map[string]interface{}{"a": []float32{1.1, 2.2}}, RunOutput: []float32{1.1, 2.2}, Output: map[string]interface{}{"a": []float32{1.1, 2.2}}}, + {Script: `a`, Input: map[string]interface{}{"a": []float64{1.1, 2.2}}, RunOutput: []float64{1.1, 2.2}, Output: map[string]interface{}{"a": []float64{1.1, 2.2}}}, + {Script: `a`, Input: map[string]interface{}{"a": []string{"a", "b"}}, RunOutput: []string{"a", "b"}, Output: map[string]interface{}{"a": []string{"a", "b"}}}, + + {Script: `a += true`, Input: map[string]interface{}{"a": []bool{}}, RunOutput: []bool{true}, Output: map[string]interface{}{"a": []bool{true}}}, + {Script: `a += 1`, Input: map[string]interface{}{"a": []int32{}}, RunOutput: []int32{1}, Output: map[string]interface{}{"a": []int32{1}}}, + {Script: `a += 1.1`, Input: map[string]interface{}{"a": []int32{}}, RunOutput: []int32{1}, Output: map[string]interface{}{"a": []int32{1}}}, + {Script: `a += 1`, Input: map[string]interface{}{"a": []int64{}}, RunOutput: []int64{1}, Output: map[string]interface{}{"a": []int64{1}}}, + {Script: `a += 1.1`, Input: map[string]interface{}{"a": []int64{}}, RunOutput: []int64{1}, Output: map[string]interface{}{"a": []int64{1}}}, + {Script: `a += 1`, Input: map[string]interface{}{"a": []float32{}}, RunOutput: []float32{1}, Output: map[string]interface{}{"a": []float32{1}}}, + {Script: `a += 1.1`, Input: map[string]interface{}{"a": []float32{}}, RunOutput: []float32{1.1}, Output: map[string]interface{}{"a": []float32{1.1}}}, + {Script: `a += 1`, Input: map[string]interface{}{"a": []float64{}}, RunOutput: []float64{1}, Output: map[string]interface{}{"a": []float64{1}}}, + {Script: `a += 1.1`, Input: map[string]interface{}{"a": []float64{}}, RunOutput: []float64{1.1}, Output: map[string]interface{}{"a": []float64{1.1}}}, + {Script: `a += "a"`, Input: map[string]interface{}{"a": []string{}}, RunOutput: []string{"a"}, Output: map[string]interface{}{"a": []string{"a"}}}, + {Script: `a += 97`, Input: map[string]interface{}{"a": []string{}}, RunOutput: []string{"a"}, Output: map[string]interface{}{"a": []string{"a"}}}, + + {Script: `a[0]`, Input: map[string]interface{}{"a": []bool{true, false}}, RunOutput: true, Output: map[string]interface{}{"a": []bool{true, false}}}, + {Script: `a[0]`, Input: map[string]interface{}{"a": []int32{1, 2}}, RunOutput: int32(1), Output: map[string]interface{}{"a": []int32{1, 2}}}, + {Script: `a[0]`, Input: map[string]interface{}{"a": []int64{1, 2}}, RunOutput: int64(1), Output: map[string]interface{}{"a": []int64{1, 2}}}, + {Script: `a[0]`, Input: map[string]interface{}{"a": []float32{1.1, 2.2}}, RunOutput: float32(1.1), Output: map[string]interface{}{"a": []float32{1.1, 2.2}}}, + {Script: `a[0]`, Input: map[string]interface{}{"a": []float64{1.1, 2.2}}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": []float64{1.1, 2.2}}}, + {Script: `a[0]`, Input: map[string]interface{}{"a": []string{"a", "b"}}, RunOutput: "a", Output: map[string]interface{}{"a": []string{"a", "b"}}}, + + {Script: `a[1]`, Input: map[string]interface{}{"a": []bool{true, false}}, RunOutput: false, Output: map[string]interface{}{"a": []bool{true, false}}}, + {Script: `a[1]`, Input: map[string]interface{}{"a": []int32{1, 2}}, RunOutput: int32(2), Output: map[string]interface{}{"a": []int32{1, 2}}}, + {Script: `a[1]`, Input: map[string]interface{}{"a": []int64{1, 2}}, RunOutput: int64(2), Output: map[string]interface{}{"a": []int64{1, 2}}}, + {Script: `a[1]`, Input: map[string]interface{}{"a": []float32{1.1, 2.2}}, RunOutput: float32(2.2), Output: map[string]interface{}{"a": []float32{1.1, 2.2}}}, + {Script: `a[1]`, Input: map[string]interface{}{"a": []float64{1.1, 2.2}}, RunOutput: float64(2.2), Output: map[string]interface{}{"a": []float64{1.1, 2.2}}}, + {Script: `a[1]`, Input: map[string]interface{}{"a": []string{"a", "b"}}, RunOutput: "b", Output: map[string]interface{}{"a": []string{"a", "b"}}}, + + {Script: `a[0]`, Input: map[string]interface{}{"a": []bool{}}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []bool{}}}, + {Script: `a[0]`, Input: map[string]interface{}{"a": []int32{}}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []int32{}}}, + {Script: `a[0]`, Input: map[string]interface{}{"a": []int64{}}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []int64{}}}, + {Script: `a[0]`, Input: map[string]interface{}{"a": []float32{}}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []float32{}}}, + {Script: `a[0]`, Input: map[string]interface{}{"a": []float64{}}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []float64{}}}, + {Script: `a[0]`, Input: map[string]interface{}{"a": []string{}}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []string{}}}, + + {Script: `a[1] = true`, Input: map[string]interface{}{"a": []bool{true, false}}, RunOutput: true, Output: map[string]interface{}{"a": []bool{true, true}}}, + {Script: `a[1] = 3`, Input: map[string]interface{}{"a": []int32{1, 2}}, RunOutput: int64(3), Output: map[string]interface{}{"a": []int32{1, 3}}}, + {Script: `a[1] = 3`, Input: map[string]interface{}{"a": []int64{1, 2}}, RunOutput: int64(3), Output: map[string]interface{}{"a": []int64{1, 3}}}, + {Script: `a[1] = 3.3`, Input: map[string]interface{}{"a": []float32{1.1, 2.2}}, RunOutput: float64(3.3), Output: map[string]interface{}{"a": []float32{1.1, 3.3}}}, + {Script: `a[1] = 3.3`, Input: map[string]interface{}{"a": []float64{1.1, 2.2}}, RunOutput: float64(3.3), Output: map[string]interface{}{"a": []float64{1.1, 3.3}}}, + {Script: `a[1] = "c"`, Input: map[string]interface{}{"a": []string{"a", "b"}}, RunOutput: "c", Output: map[string]interface{}{"a": []string{"a", "c"}}}, + + {Script: `a = []; a[0]`, RunError: fmt.Errorf("index out of range")}, + {Script: `a = []; a[-1]`, RunError: fmt.Errorf("index out of range")}, + {Script: `a = []; a[1] = 1`, RunError: fmt.Errorf("index out of range")}, + {Script: `a = []; a[-1] = 1`, RunError: fmt.Errorf("index out of range")}, + + {Script: `b = [1, 2]; b[a]`, Input: map[string]interface{}{"a": nil}, RunError: fmt.Errorf("index must be a number"), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(2)}}}, + {Script: `b = [1, 2]; b[a]`, Input: map[string]interface{}{"a": true}, RunOutput: int64(2), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(2)}}}, + {Script: `b = [1, 2]; b[a]`, Input: map[string]interface{}{"a": int(1)}, RunOutput: int64(2), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(2)}}}, + {Script: `b = [1, 2]; b[a]`, Input: map[string]interface{}{"a": int32(1)}, RunOutput: int64(2), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(2)}}}, + {Script: `b = [1, 2]; b[a]`, Input: map[string]interface{}{"a": int64(1)}, RunOutput: int64(2), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(2)}}}, + {Script: `b = [1, 2]; b[a]`, Input: map[string]interface{}{"a": float32(1.1)}, RunOutput: int64(2), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(2)}}}, + {Script: `b = [1, 2]; b[a]`, Input: map[string]interface{}{"a": float64(1.1)}, RunOutput: int64(2), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(2)}}}, + {Script: `b = [1, 2]; b[a]`, Input: map[string]interface{}{"a": "1"}, RunOutput: int64(2), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(2)}}}, + {Script: `b = [1, 2]; b[a]`, Input: map[string]interface{}{"a": "a"}, RunError: fmt.Errorf("index must be a number"), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(2)}}}, + + {Script: `b = [1, 2]; b[a]`, Input: map[string]interface{}{"a": testVarBoolP}, RunOutput: int64(2), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(2)}}}, + {Script: `b = [1, 2]; b[a]`, Input: map[string]interface{}{"a": testVarInt32P}, RunOutput: int64(2), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(2)}}}, + {Script: `b = [1, 2]; b[a]`, Input: map[string]interface{}{"a": testVarInt64P}, RunOutput: int64(2), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(2)}}}, + {Script: `b = [1, 2]; b[a]`, Input: map[string]interface{}{"a": testVarFloat32P}, RunOutput: int64(2), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(2)}}}, + {Script: `b = [1, 2]; b[a]`, Input: map[string]interface{}{"a": testVarFloat64P}, RunOutput: int64(2), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(2)}}}, + {Script: `b = [1, 2]; b[a]`, Input: map[string]interface{}{"a": testVarStringP}, RunError: fmt.Errorf("index must be a number"), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(2)}}}, + + {Script: `b = [1, 2]; b[a] = 3`, Input: map[string]interface{}{"a": nil}, RunError: fmt.Errorf("index must be a number"), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(2)}}}, + {Script: `b = [1, 2]; b[a] = 3`, Input: map[string]interface{}{"a": true}, RunOutput: int64(3), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(3)}}}, + {Script: `b = [1, 2]; b[a] = 3`, Input: map[string]interface{}{"a": int(1)}, RunOutput: int64(3), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(3)}}}, + {Script: `b = [1, 2]; b[a] = 3`, Input: map[string]interface{}{"a": int32(1)}, RunOutput: int64(3), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(3)}}}, + {Script: `b = [1, 2]; b[a] = 3`, Input: map[string]interface{}{"a": int64(1)}, RunOutput: int64(3), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(3)}}}, + {Script: `b = [1, 2]; b[a] = 3`, Input: map[string]interface{}{"a": float32(1.1)}, RunOutput: int64(3), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(3)}}}, + {Script: `b = [1, 2]; b[a] = 3`, Input: map[string]interface{}{"a": float64(1.1)}, RunOutput: int64(3), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(3)}}}, + {Script: `b = [1, 2]; b[a] = 3`, Input: map[string]interface{}{"a": "1"}, RunOutput: int64(3), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(3)}}}, + {Script: `b = [1, 2]; b[a] = 3`, Input: map[string]interface{}{"a": "a"}, RunError: fmt.Errorf("index must be a number"), Output: map[string]interface{}{"b": []interface{}{int64(1), int64(2)}}}, + + {Script: `a`, Input: map[string]interface{}{"a": testSliceEmpty}, RunOutput: testSliceEmpty, Output: map[string]interface{}{"a": testSliceEmpty}}, + {Script: `a += []`, Input: map[string]interface{}{"a": testSliceEmpty}, RunOutput: []interface{}(nil), Output: map[string]interface{}{"a": testSliceEmpty}}, + + {Script: `a`, Input: map[string]interface{}{"a": testSlice}, RunOutput: testSlice, Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[0]`, Input: map[string]interface{}{"a": testSlice}, RunOutput: nil, Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[0] = 1`, Input: map[string]interface{}{"a": testSlice}, RunOutput: int64(1), Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[0]`, Input: map[string]interface{}{"a": testSlice}, RunOutput: int64(1), Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[0] = nil`, Input: map[string]interface{}{"a": testSlice}, RunOutput: nil, Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[0]`, Input: map[string]interface{}{"a": testSlice}, RunOutput: nil, Output: map[string]interface{}{"a": testSlice}}, + + {Script: `a[1]`, Input: map[string]interface{}{"a": testSlice}, RunOutput: true, Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[1] = false`, Input: map[string]interface{}{"a": testSlice}, RunOutput: false, Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[1]`, Input: map[string]interface{}{"a": testSlice}, RunOutput: false, Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[1] = true`, Input: map[string]interface{}{"a": testSlice}, RunOutput: true, Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[1]`, Input: map[string]interface{}{"a": testSlice}, RunOutput: true, Output: map[string]interface{}{"a": testSlice}}, + + {Script: `a[2]`, Input: map[string]interface{}{"a": testSlice}, RunOutput: int64(1), Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[2] = 2`, Input: map[string]interface{}{"a": testSlice}, RunOutput: int64(2), Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[2]`, Input: map[string]interface{}{"a": testSlice}, RunOutput: int64(2), Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[2] = 1`, Input: map[string]interface{}{"a": testSlice}, RunOutput: int64(1), Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[2]`, Input: map[string]interface{}{"a": testSlice}, RunOutput: int64(1), Output: map[string]interface{}{"a": testSlice}}, + + {Script: `a[3]`, Input: map[string]interface{}{"a": testSlice}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[3] = 2.2`, Input: map[string]interface{}{"a": testSlice}, RunOutput: float64(2.2), Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[3]`, Input: map[string]interface{}{"a": testSlice}, RunOutput: float64(2.2), Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[3] = 1.1`, Input: map[string]interface{}{"a": testSlice}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[3]`, Input: map[string]interface{}{"a": testSlice}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": testSlice}}, + + {Script: `a[4]`, Input: map[string]interface{}{"a": testSlice}, RunOutput: "a", Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[4] = "x"`, Input: map[string]interface{}{"a": testSlice}, RunOutput: "x", Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[4]`, Input: map[string]interface{}{"a": testSlice}, RunOutput: "x", Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[4] = "a"`, Input: map[string]interface{}{"a": testSlice}, RunOutput: "a", Output: map[string]interface{}{"a": testSlice}}, + {Script: `a[4]`, Input: map[string]interface{}{"a": testSlice}, RunOutput: "a", Output: map[string]interface{}{"a": testSlice}}, + + {Script: `a[0][0] = true`, Input: map[string]interface{}{"a": []interface{}{[]string{"a"}}}, RunError: fmt.Errorf("type bool cannot be assigned to type string for slice index"), Output: map[string]interface{}{"a": []interface{}{[]string{"a"}}}}, + {Script: `a[0][0] = "a"`, Input: map[string]interface{}{"a": []interface{}{[]bool{true}}}, RunError: fmt.Errorf("type string cannot be assigned to type bool for slice index"), Output: map[string]interface{}{"a": []interface{}{[]bool{true}}}}, + + {Script: `a[0][0] = b[0][0]`, Input: map[string]interface{}{"a": []interface{}{[]bool{true}}, "b": []interface{}{[]string{"b"}}}, RunError: fmt.Errorf("type string cannot be assigned to type bool for slice index"), Output: map[string]interface{}{"a": []interface{}{[]bool{true}}}}, + {Script: `b[0][0] = a[0][0]`, Input: map[string]interface{}{"a": []interface{}{[]bool{true}}, "b": []interface{}{[]string{"b"}}}, RunError: fmt.Errorf("type bool cannot be assigned to type string for slice index"), Output: map[string]interface{}{"a": []interface{}{[]bool{true}}}}, + + {Script: `a = make([][]bool); a[0] = make([]bool); a[0] = [true, 1]`, RunError: fmt.Errorf("type []interface {} cannot be assigned to type []bool for slice index"), Output: map[string]interface{}{"a": [][]bool{{}}}}, + {Script: `a = make([][]bool); a[0] = make([]bool); a[0] = [true, false]`, RunOutput: []interface{}{true, false}, Output: map[string]interface{}{"a": [][]bool{{true, false}}}}, + + {Script: `a = make([][][]bool); a[0] = make([][]bool); a[0][0] = make([]bool); a[0] = [[true, 1]]`, RunError: fmt.Errorf("type []interface {} cannot be assigned to type [][]bool for slice index"), Output: map[string]interface{}{"a": [][][]bool{{{}}}}}, + {Script: `a = make([][][]bool); a[0] = make([][]bool); a[0][0] = make([]bool); a[0] = [[true, false]]`, RunOutput: []interface{}{[]interface{}{true, false}}, Output: map[string]interface{}{"a": [][][]bool{{{true, false}}}}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestSlicesAutoAppend(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `a[2]`, Input: map[string]interface{}{"a": []bool{true, false}}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []bool{true, false}}}, + {Script: `a[2]`, Input: map[string]interface{}{"a": []int32{1, 2}}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []int32{1, 2}}}, + {Script: `a[2]`, Input: map[string]interface{}{"a": []int64{1, 2}}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []int64{1, 2}}}, + {Script: `a[2]`, Input: map[string]interface{}{"a": []float32{1.1, 2.2}}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []float32{1.1, 2.2}}}, + {Script: `a[2]`, Input: map[string]interface{}{"a": []float64{1.1, 2.2}}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []float64{1.1, 2.2}}}, + {Script: `a[2]`, Input: map[string]interface{}{"a": []string{"a", "b"}}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []string{"a", "b"}}}, + + {Script: `a[2] = true`, Input: map[string]interface{}{"a": []bool{true, false}}, RunOutput: true, Output: map[string]interface{}{"a": []bool{true, false, true}}}, + {Script: `a[2] = 3`, Input: map[string]interface{}{"a": []int32{1, 2}}, RunOutput: int64(3), Output: map[string]interface{}{"a": []int32{1, 2, 3}}}, + {Script: `a[2] = 3`, Input: map[string]interface{}{"a": []int64{1, 2}}, RunOutput: int64(3), Output: map[string]interface{}{"a": []int64{1, 2, 3}}}, + {Script: `a[2] = 3.3`, Input: map[string]interface{}{"a": []float32{1.1, 2.2}}, RunOutput: float64(3.3), Output: map[string]interface{}{"a": []float32{1.1, 2.2, 3.3}}}, + {Script: `a[2] = 3.3`, Input: map[string]interface{}{"a": []float64{1.1, 2.2}}, RunOutput: float64(3.3), Output: map[string]interface{}{"a": []float64{1.1, 2.2, 3.3}}}, + {Script: `a[2] = "c"`, Input: map[string]interface{}{"a": []string{"a", "b"}}, RunOutput: "c", Output: map[string]interface{}{"a": []string{"a", "b", "c"}}}, + + {Script: `a[2] = 3.3`, Input: map[string]interface{}{"a": []int32{1, 2}}, RunOutput: float64(3.3), Output: map[string]interface{}{"a": []int32{1, 2, 3}}}, + {Script: `a[2] = 3.3`, Input: map[string]interface{}{"a": []int64{1, 2}}, RunOutput: float64(3.3), Output: map[string]interface{}{"a": []int64{1, 2, 3}}}, + + {Script: `a[3] = true`, Input: map[string]interface{}{"a": []bool{true, false}}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []bool{true, false}}}, + {Script: `a[3] = 4`, Input: map[string]interface{}{"a": []int32{1, 2}}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []int32{1, 2}}}, + {Script: `a[3] = 4`, Input: map[string]interface{}{"a": []int64{1, 2}}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []int64{1, 2}}}, + {Script: `a[3] = 4.4`, Input: map[string]interface{}{"a": []float32{1.1, 2.2}}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []float32{1.1, 2.2}}}, + {Script: `a[3] = 4.4`, Input: map[string]interface{}{"a": []float64{1.1, 2.2}}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []float64{1.1, 2.2}}}, + {Script: `a[3] = "d"`, Input: map[string]interface{}{"a": []string{"a", "b"}}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []string{"a", "b"}}}, + + {Script: `a[2] = nil`, Input: map[string]interface{}{"a": []bool{true, false}}, Output: map[string]interface{}{"a": []bool{true, false, false}}}, + {Script: `a[2] = nil`, Input: map[string]interface{}{"a": []int32{1, 2}}, Output: map[string]interface{}{"a": []int32{1, 2, 0}}}, + {Script: `a[2] = nil`, Input: map[string]interface{}{"a": []int64{1, 2}}, Output: map[string]interface{}{"a": []int64{1, 2, 0}}}, + {Script: `a[2] = nil`, Input: map[string]interface{}{"a": []float32{1.5, 2.5}}, Output: map[string]interface{}{"a": []float32{1.5, 2.5, 0}}}, + {Script: `a[2] = nil`, Input: map[string]interface{}{"a": []float64{1.5, 2.5}}, Output: map[string]interface{}{"a": []float64{1.5, 2.5, 0}}}, + {Script: `a[2] = nil`, Input: map[string]interface{}{"a": []string{"a", "b"}}, Output: map[string]interface{}{"a": []string{"a", "b", ""}}}, + + {Script: `a[2] = "a"`, Input: map[string]interface{}{"a": []int16{1, 2}}, RunError: fmt.Errorf("type string cannot be assigned to type int16 for slice index"), Output: map[string]interface{}{"a": []int16{1, 2}}}, + {Script: `a[2] = true`, Input: map[string]interface{}{"a": []int64{1, 2}}, RunError: fmt.Errorf("type bool cannot be assigned to type int64 for slice index"), Output: map[string]interface{}{"a": []int64{1, 2}}}, + {Script: `a[2] = "a"`, Input: map[string]interface{}{"a": []int64{1, 2}}, RunError: fmt.Errorf("type string cannot be assigned to type int64 for slice index"), Output: map[string]interface{}{"a": []int64{1, 2}}}, + {Script: `a[2] = true`, Input: map[string]interface{}{"a": []float32{1.1, 2.2}}, RunError: fmt.Errorf("type bool cannot be assigned to type float32 for slice index"), Output: map[string]interface{}{"a": []float32{1.1, 2.2}}}, + {Script: `a[2] = "a"`, Input: map[string]interface{}{"a": []float64{1.1, 2.2}}, RunError: fmt.Errorf("type string cannot be assigned to type float64 for slice index"), Output: map[string]interface{}{"a": []float64{1.1, 2.2}}}, + {Script: `a[2] = true`, Input: map[string]interface{}{"a": []string{"a", "b"}}, RunError: fmt.Errorf("type bool cannot be assigned to type string for slice index"), Output: map[string]interface{}{"a": []string{"a", "b"}}}, + {Script: `a[2] = 1.1`, Input: map[string]interface{}{"a": []string{"a", "b"}}, RunError: fmt.Errorf("type float64 cannot be assigned to type string for slice index"), Output: map[string]interface{}{"a": []string{"a", "b"}}}, + + {Script: `a`, Input: map[string]interface{}{"a": [][]interface{}{}}, RunOutput: [][]interface{}{}, Output: map[string]interface{}{"a": [][]interface{}{}}}, + {Script: `a[0] = []`, Input: map[string]interface{}{"a": [][]interface{}{}}, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": [][]interface{}{{}}}}, + {Script: `a[0] = [nil]`, Input: map[string]interface{}{"a": [][]interface{}{}}, RunOutput: []interface{}{nil}, Output: map[string]interface{}{"a": [][]interface{}{{nil}}}}, + {Script: `a[0] = [true]`, Input: map[string]interface{}{"a": [][]interface{}{}}, RunOutput: []interface{}{true}, Output: map[string]interface{}{"a": [][]interface{}{{true}}}}, + {Script: `a[0] = [1]`, Input: map[string]interface{}{"a": [][]interface{}{}}, RunOutput: []interface{}{int64(1)}, Output: map[string]interface{}{"a": [][]interface{}{{int64(1)}}}}, + {Script: `a[0] = [1.1]`, Input: map[string]interface{}{"a": [][]interface{}{}}, RunOutput: []interface{}{float64(1.1)}, Output: map[string]interface{}{"a": [][]interface{}{{float64(1.1)}}}}, + {Script: `a[0] = ["b"]`, Input: map[string]interface{}{"a": [][]interface{}{}}, RunOutput: []interface{}{"b"}, Output: map[string]interface{}{"a": [][]interface{}{{"b"}}}}, + + {Script: `a[0] = [nil]; a[0][0]`, Input: map[string]interface{}{"a": [][]interface{}{}}, RunOutput: nil, Output: map[string]interface{}{"a": [][]interface{}{{nil}}}}, + {Script: `a[0] = [true]; a[0][0]`, Input: map[string]interface{}{"a": [][]interface{}{}}, RunOutput: true, Output: map[string]interface{}{"a": [][]interface{}{{true}}}}, + {Script: `a[0] = [1]; a[0][0]`, Input: map[string]interface{}{"a": [][]interface{}{}}, RunOutput: int64(1), Output: map[string]interface{}{"a": [][]interface{}{{int64(1)}}}}, + {Script: `a[0] = [1.1]; a[0][0]`, Input: map[string]interface{}{"a": [][]interface{}{}}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": [][]interface{}{{float64(1.1)}}}}, + {Script: `a[0] = ["b"]; a[0][0]`, Input: map[string]interface{}{"a": [][]interface{}{}}, RunOutput: "b", Output: map[string]interface{}{"a": [][]interface{}{{"b"}}}}, + + {Script: `a = make([]bool); a[0] = 1`, RunError: fmt.Errorf("type int64 cannot be assigned to type bool for slice index"), Output: map[string]interface{}{"a": []bool{}}}, + {Script: `a = make([]bool); a[0] = true; a[1] = false`, RunOutput: false, Output: map[string]interface{}{"a": []bool{true, false}}}, + + {Script: `a = make([][]bool); a[0] = [true, 1]`, RunError: fmt.Errorf("type []interface {} cannot be assigned to type []bool for slice index"), Output: map[string]interface{}{"a": [][]bool{}}}, + {Script: `a = make([][]bool); a[0] = [true, false]`, RunOutput: []interface{}{true, false}, Output: map[string]interface{}{"a": [][]bool{{true, false}}}}, + + {Script: `a = make([][][]bool); a[0] = [[true, 1]]`, RunError: fmt.Errorf("type []interface {} cannot be assigned to type [][]bool for slice index"), Output: map[string]interface{}{"a": [][][]bool{}}}, + {Script: `a = make([][][]bool); a[0] = [[true, false]]`, RunOutput: []interface{}{[]interface{}{true, false}}, Output: map[string]interface{}{"a": [][][]bool{{{true, false}}}}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestMakeSlices(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `make([]foo)`, RunError: fmt.Errorf("undefined type 'foo'")}, + + {Script: `make([]nilT)`, Types: map[string]interface{}{"nilT": nil}, RunError: fmt.Errorf("cannot make type nil")}, + {Script: `make([][]nilT)`, Types: map[string]interface{}{"nilT": nil}, RunError: fmt.Errorf("cannot make type nil")}, + {Script: `make([][][]nilT)`, Types: map[string]interface{}{"nilT": nil}, RunError: fmt.Errorf("cannot make type nil")}, + + {Script: `make([]bool, 1++)`, RunError: fmt.Errorf("invalid operation")}, + {Script: `make([]bool, 0, 1++)`, RunError: fmt.Errorf("invalid operation")}, + + // spaces and/or newlines + {Script: `[]`, RunOutput: []interface{}{}}, + {Script: `[ ]`, RunOutput: []interface{}{}}, + {Script: `[ +]`, RunOutput: []interface{}{}}, + {Script: `[ + ]`, RunOutput: []interface{}{}}, + + {Script: `make(slice2x)`, Types: map[string]interface{}{"slice2x": [][]interface{}{}}, RunOutput: [][]interface{}{}}, + + {Script: `make([]bool)`, RunOutput: []bool{}}, + {Script: `make([]int32)`, RunOutput: []int32{}}, + {Script: `make([]int64)`, RunOutput: []int64{}}, + {Script: `make([]float32)`, RunOutput: []float32{}}, + {Script: `make([]float64)`, RunOutput: []float64{}}, + {Script: `make([]string)`, RunOutput: []string{}}, + + {Script: `make([]bool, 0)`, RunOutput: []bool{}}, + {Script: `make([]int32, 0)`, RunOutput: []int32{}}, + {Script: `make([]int64, 0)`, RunOutput: []int64{}}, + {Script: `make([]float32, 0)`, RunOutput: []float32{}}, + {Script: `make([]float64, 0)`, RunOutput: []float64{}}, + {Script: `make([]string, 0)`, RunOutput: []string{}}, + + {Script: `make([]bool, 2)`, RunOutput: []bool{false, false}}, + {Script: `make([]int32, 2)`, RunOutput: []int32{int32(0), int32(0)}}, + {Script: `make([]int64, 2)`, RunOutput: []int64{int64(0), int64(0)}}, + {Script: `make([]float32, 2)`, RunOutput: []float32{float32(0), float32(0)}}, + {Script: `make([]float64, 2)`, RunOutput: []float64{float64(0), float64(0)}}, + {Script: `make([]string, 2)`, RunOutput: []string{"", ""}}, + + {Script: `make([]bool, 0, 2)`, RunOutput: []bool{}}, + {Script: `make([]int32, 0, 2)`, RunOutput: []int32{}}, + {Script: `make([]int64, 0, 2)`, RunOutput: []int64{}}, + {Script: `make([]float32, 0, 2)`, RunOutput: []float32{}}, + {Script: `make([]float64, 0, 2)`, RunOutput: []float64{}}, + {Script: `make([]string, 0, 2)`, RunOutput: []string{}}, + + {Script: `make([]bool, 2, 2)`, RunOutput: []bool{false, false}}, + {Script: `make([]int32, 2, 2)`, RunOutput: []int32{int32(0), int32(0)}}, + {Script: `make([]int64, 2, 2)`, RunOutput: []int64{int64(0), int64(0)}}, + {Script: `make([]float32, 2, 2)`, RunOutput: []float32{float32(0), float32(0)}}, + {Script: `make([]float64, 2, 2)`, RunOutput: []float64{float64(0), float64(0)}}, + {Script: `make([]string, 2, 2)`, RunOutput: []string{"", ""}}, + + {Script: `a = make([]bool, 0); a += true; a += false`, RunOutput: []bool{true, false}, Output: map[string]interface{}{"a": []bool{true, false}}}, + {Script: `a = make([]int32, 0); a += 1; a += 2`, RunOutput: []int32{int32(1), int32(2)}, Output: map[string]interface{}{"a": []int32{int32(1), int32(2)}}}, + {Script: `a = make([]int64, 0); a += 1; a += 2`, RunOutput: []int64{int64(1), int64(2)}, Output: map[string]interface{}{"a": []int64{int64(1), int64(2)}}}, + {Script: `a = make([]float32, 0); a += 1.1; a += 2.2`, RunOutput: []float32{float32(1.1), float32(2.2)}, Output: map[string]interface{}{"a": []float32{float32(1.1), float32(2.2)}}}, + {Script: `a = make([]float64, 0); a += 1.1; a += 2.2`, RunOutput: []float64{float64(1.1), float64(2.2)}, Output: map[string]interface{}{"a": []float64{float64(1.1), float64(2.2)}}}, + {Script: `a = make([]string, 0); a += "a"; a += "b"`, RunOutput: []string{"a", "b"}, Output: map[string]interface{}{"a": []string{"a", "b"}}}, + + {Script: `a = make([]bool, 2); a[0] = true; a[1] = false`, RunOutput: false, Output: map[string]interface{}{"a": []bool{true, false}}}, + {Script: `a = make([]int32, 2); a[0] = 1; a[1] = 2`, RunOutput: int64(2), Output: map[string]interface{}{"a": []int32{int32(1), int32(2)}}}, + {Script: `a = make([]int64, 2); a[0] = 1; a[1] = 2`, RunOutput: int64(2), Output: map[string]interface{}{"a": []int64{int64(1), int64(2)}}}, + {Script: `a = make([]float32, 2); a[0] = 1.1; a[1] = 2.2`, RunOutput: float64(2.2), Output: map[string]interface{}{"a": []float32{float32(1.1), float32(2.2)}}}, + {Script: `a = make([]float64, 2); a[0] = 1.1; a[1] = 2.2`, RunOutput: float64(2.2), Output: map[string]interface{}{"a": []float64{float64(1.1), float64(2.2)}}}, + {Script: `a = make([]string, 2); a[0] = "a"; a[1] = "b"`, RunOutput: "b", Output: map[string]interface{}{"a": []string{"a", "b"}}}, + + {Script: `make([]boolA)`, Types: map[string]interface{}{"boolA": []bool{}}, RunOutput: [][]bool{}}, + {Script: `make([]int32A)`, Types: map[string]interface{}{"int32A": []int32{}}, RunOutput: [][]int32{}}, + {Script: `make([]int64A)`, Types: map[string]interface{}{"int64A": []int64{}}, RunOutput: [][]int64{}}, + {Script: `make([]float32A)`, Types: map[string]interface{}{"float32A": []float32{}}, RunOutput: [][]float32{}}, + {Script: `make([]float64A)`, Types: map[string]interface{}{"float64A": []float64{}}, RunOutput: [][]float64{}}, + {Script: `make([]stringA)`, Types: map[string]interface{}{"stringA": []string{}}, RunOutput: [][]string{}}, + + {Script: `make([]slice)`, Types: map[string]interface{}{"slice": []interface{}{}}, RunOutput: [][]interface{}{}}, + {Script: `a = make([]slice)`, Types: map[string]interface{}{"slice": []interface{}{}}, RunOutput: [][]interface{}{}, Output: map[string]interface{}{"a": [][]interface{}{}}}, + + {Script: `make([][]bool)`, RunOutput: [][]bool{}}, + {Script: `make([][]int32)`, RunOutput: [][]int32{}}, + {Script: `make([][]int64)`, RunOutput: [][]int64{}}, + {Script: `make([][]float32)`, RunOutput: [][]float32{}}, + {Script: `make([][]float64)`, RunOutput: [][]float64{}}, + {Script: `make([][]string)`, RunOutput: [][]string{}}, + + {Script: `make([][]bool)`, RunOutput: [][]bool{}}, + {Script: `make([][]int32)`, RunOutput: [][]int32{}}, + {Script: `make([][]int64)`, RunOutput: [][]int64{}}, + {Script: `make([][]float32)`, RunOutput: [][]float32{}}, + {Script: `make([][]float64)`, RunOutput: [][]float64{}}, + {Script: `make([][]string)`, RunOutput: [][]string{}}, + + {Script: `make([][][]bool)`, RunOutput: [][][]bool{}}, + {Script: `make([][][]int32)`, RunOutput: [][][]int32{}}, + {Script: `make([][][]int64)`, RunOutput: [][][]int64{}}, + {Script: `make([][][]float32)`, RunOutput: [][][]float32{}}, + {Script: `make([][][]float64)`, RunOutput: [][][]float64{}}, + {Script: `make([][][]string)`, RunOutput: [][][]string{}}, + + // slice type errors + {Script: `[]nilT{"a"}`, Types: map[string]interface{}{"nilT": nil}, RunError: fmt.Errorf("cannot make type nil")}, + {Script: `[]int64{1++}`, RunError: fmt.Errorf("invalid operation")}, + {Script: `[]int64{"a"}`, RunError: fmt.Errorf("cannot use type string as type int64 as slice value")}, + + // slice type + {Script: `[]interface{nil}`, RunOutput: []interface{}{nil}}, + {Script: `[]bool{true, false}`, RunOutput: []bool{true, false}}, + {Script: `[]int32{1}`, RunOutput: []int32{int32(1)}}, + {Script: `[]int64{2}`, RunOutput: []int64{int64(2)}}, + {Script: `[]float32{3.5}`, RunOutput: []float32{float32(3.5)}}, + {Script: `[]float64{4.5}`, RunOutput: []float64{float64(4.5)}}, + {Script: `[]string{"a"}`, RunOutput: []string{"a"}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestSliceOfSlices(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `a = [1, 2]; a[:]`, ParseError: fmt.Errorf("syntax error")}, + {Script: `(1++)[0:0]`, RunError: fmt.Errorf("invalid operation")}, + {Script: `a = [1, 2]; a[1++:0]`, RunError: fmt.Errorf("invalid operation"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2)}}}, + {Script: `a = [1, 2]; a[0:1++]`, RunError: fmt.Errorf("invalid operation"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2)}}}, + {Script: `a = [1, 2]; a[:0]++`, RunError: fmt.Errorf("slice cannot be assigned"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2)}}}, + {Script: `a = [1, 2]; a[:0]--`, RunError: fmt.Errorf("slice cannot be assigned"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2)}}}, + + {Script: `a = [1, 2, 3]; a[nil:2]`, RunError: fmt.Errorf("index must be a number"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[1:nil]`, RunError: fmt.Errorf("index must be a number"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + + {Script: `a = [1, 2, 3]; a[-1:0]`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[0:0]`, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[0:1]`, RunOutput: []interface{}{int64(1)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[0:2]`, RunOutput: []interface{}{int64(1), int64(2)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[0:3]`, RunOutput: []interface{}{int64(1), int64(2), int64(3)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[0:4]`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + + {Script: `a = [1, 2, 3]; a[1:0]`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[1:1]`, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[1:2]`, RunOutput: []interface{}{int64(2)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[1:3]`, RunOutput: []interface{}{int64(2), int64(3)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[1:4]`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + + {Script: `a = [1, 2, 3]; a[2:1]`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[2:2]`, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[2:3]`, RunOutput: []interface{}{int64(3)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[2:4]`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + + {Script: `a = [1, 2, 3]; a[3:2]`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[3:3]`, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[3:4]`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + + {Script: `a = [1, 2, 3]; a[4:4]`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + + {Script: `a = [1, 2, 3]; a[-1:]`, RunError: fmt.Errorf("index out of range"), RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[0:]`, RunOutput: []interface{}{int64(1), int64(2), int64(3)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[1:]`, RunOutput: []interface{}{int64(2), int64(3)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[2:]`, RunOutput: []interface{}{int64(3)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[3:]`, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[4:]`, RunError: fmt.Errorf("index out of range"), RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + + {Script: `a = [1, 2, 3]; a[:-1]`, RunError: fmt.Errorf("index out of range"), RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[:0]`, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[:1]`, RunOutput: []interface{}{int64(1)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[:2]`, RunOutput: []interface{}{int64(1), int64(2)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[:3]`, RunOutput: []interface{}{int64(1), int64(2), int64(3)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[:4]`, RunError: fmt.Errorf("index out of range"), RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + + {Script: `b[1:2] = 4`, RunError: fmt.Errorf("undefined symbol 'b'")}, + {Script: `a = [1, 2, 3]; a[1++:2] = 4`, RunError: fmt.Errorf("invalid operation"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[1:2++] = 4`, RunError: fmt.Errorf("invalid operation"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[nil:2] = 4`, RunError: fmt.Errorf("index must be a number"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[1:nil] = 4`, RunError: fmt.Errorf("index must be a number"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + + {Script: `a = [1, 2, 3]; a[0:0] = 4`, RunError: fmt.Errorf("slice cannot be assigned"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[0:1] = 4`, RunError: fmt.Errorf("slice cannot be assigned"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[0:4] = 4`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[1:0] = 4`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[1:4] = 4`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + + {Script: `a = [1, 2, 3]; a[-1:] = 4`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[0:] = 4`, RunError: fmt.Errorf("slice cannot be assigned"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[1:] = 4`, RunError: fmt.Errorf("slice cannot be assigned"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[4:] = 4`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + + {Script: `a = [1, 2, 3]; a[:-1] = 4`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[:0] = 4`, RunError: fmt.Errorf("slice cannot be assigned"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[:1] = 4`, RunError: fmt.Errorf("slice cannot be assigned"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = [1, 2, 3]; a[:4] = 4`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + + // slice assigned elem + {Script: `a[0][0:0] = 4`, Input: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}}}, RunError: fmt.Errorf("slice cannot be assigned"), Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}}}}, + + {Script: `a = [{"b": "b"}, {"c": "c"}, {"d": "d"}]; a[0:2].a`, RunError: fmt.Errorf("type slice does not support member operation"), Output: map[string]interface{}{"a": []interface{}{map[interface{}]interface{}{"b": "b"}, map[interface{}]interface{}{"c": "c"}, map[interface{}]interface{}{"d": "d"}}}}, + + {Script: `a = [{"b": "b"}, {"c": "c"}, {"d": "d"}]; a[0:2]`, RunOutput: []interface{}{map[interface{}]interface{}{"b": "b"}, map[interface{}]interface{}{"c": "c"}}, Output: map[string]interface{}{"a": []interface{}{map[interface{}]interface{}{"b": "b"}, map[interface{}]interface{}{"c": "c"}, map[interface{}]interface{}{"d": "d"}}}}, + {Script: `a = [{"b": "b"}, {"c": "c"}, {"d": "d"}]; a[0:2][0].b`, RunOutput: "b", Output: map[string]interface{}{"a": []interface{}{map[interface{}]interface{}{"b": "b"}, map[interface{}]interface{}{"c": "c"}, map[interface{}]interface{}{"d": "d"}}}}, + + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[0][0:3]`, RunOutput: []interface{}{int64(1), int64(2), int64(3)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[0][1:3]`, RunOutput: []interface{}{int64(2), int64(3)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[0][2:3]`, RunOutput: []interface{}{int64(3)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[0][3:3]`, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[0][0:]`, RunOutput: []interface{}{int64(1), int64(2), int64(3)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[0][1:]`, RunOutput: []interface{}{int64(2), int64(3)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[0][2:]`, RunOutput: []interface{}{int64(3)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[0][3:]`, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[0][0:0]`, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[0][0:1]`, RunOutput: []interface{}{int64(1)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[0][0:2]`, RunOutput: []interface{}{int64(1), int64(2)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[0][0:3]`, RunOutput: []interface{}{int64(1), int64(2), int64(3)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[0][:0]`, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[0][:1]`, RunOutput: []interface{}{int64(1)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[0][:2]`, RunOutput: []interface{}{int64(1), int64(2)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[0][:3]`, RunOutput: []interface{}{int64(1), int64(2), int64(3)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[1][0:3]`, RunOutput: []interface{}{int64(4), int64(5), int64(6)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[1][1:3]`, RunOutput: []interface{}{int64(5), int64(6)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[1][2:3]`, RunOutput: []interface{}{int64(6)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[1][3:3]`, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[1][0:]`, RunOutput: []interface{}{int64(4), int64(5), int64(6)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[1][1:]`, RunOutput: []interface{}{int64(5), int64(6)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[1][2:]`, RunOutput: []interface{}{int64(6)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[1][3:]`, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[1][0:0]`, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[1][0:1]`, RunOutput: []interface{}{int64(4)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[1][0:2]`, RunOutput: []interface{}{int64(4), int64(5)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[1][0:3]`, RunOutput: []interface{}{int64(4), int64(5), int64(6)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[1][:0]`, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[1][:1]`, RunOutput: []interface{}{int64(4)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[1][:2]`, RunOutput: []interface{}{int64(4), int64(5)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + {Script: `a = [[1, 2, 3], [4, 5, 6]]; a[1][:3]`, RunOutput: []interface{}{int64(4), int64(5), int64(6)}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2), int64(3)}, []interface{}{int64(4), int64(5), int64(6)}}}}, + + {Script: `a = [["123"], ["456"]]; a[0][0][0:3]`, RunOutput: "123", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[0][0][1:3]`, RunOutput: "23", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[0][0][2:3]`, RunOutput: "3", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[0][0][3:3]`, RunOutput: "", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + + {Script: `a = [["123"], ["456"]]; a[0][0][0:]`, RunOutput: "123", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[0][0][1:]`, RunOutput: "23", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[0][0][2:]`, RunOutput: "3", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[0][0][3:]`, RunOutput: "", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + + {Script: `a = [["123"], ["456"]]; a[0][0][0:0]`, RunOutput: "", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[0][0][0:1]`, RunOutput: "1", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[0][0][0:2]`, RunOutput: "12", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[0][0][0:3]`, RunOutput: "123", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + + {Script: `a = [["123"], ["456"]]; a[0][0][:0]`, RunOutput: "", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[0][0][:1]`, RunOutput: "1", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[0][0][:2]`, RunOutput: "12", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[0][0][:3]`, RunOutput: "123", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + + {Script: `a = [["123"], ["456"]]; a[1][0][0:3]`, RunOutput: "456", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[1][0][1:3]`, RunOutput: "56", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[1][0][2:3]`, RunOutput: "6", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[1][0][3:3]`, RunOutput: "", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + + {Script: `a = [["123"], ["456"]]; a[1][0][0:]`, RunOutput: "456", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[1][0][1:]`, RunOutput: "56", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[1][0][2:]`, RunOutput: "6", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[1][0][3:]`, RunOutput: "", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + + {Script: `a = [["123"], ["456"]]; a[1][0][0:0]`, RunOutput: "", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[1][0][0:1]`, RunOutput: "4", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[1][0][0:2]`, RunOutput: "45", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[1][0][0:3]`, RunOutput: "456", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + + {Script: `a = [["123"], ["456"]]; a[1][0][:0]`, RunOutput: "", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[1][0][:1]`, RunOutput: "4", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[1][0][:2]`, RunOutput: "45", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + {Script: `a = [["123"], ["456"]]; a[1][0][:3]`, RunOutput: "456", Output: map[string]interface{}{"a": []interface{}{[]interface{}{"123"}, []interface{}{"456"}}}}, + + // cap errors + {Script: `a = "a"; a[0:1:1]`, RunError: fmt.Errorf("type string does not support cap")}, + {Script: `a = [1,2,3,4]; a[1:3:-1]`, RunError: fmt.Errorf("cap out of range")}, + {Script: `a = [1,2,3,4]; a[1:3:0]`, RunError: fmt.Errorf("cap out of range")}, + {Script: `a = [1,2,3,4]; a[1:3:2]`, RunError: fmt.Errorf("cap out of range")}, + {Script: `a = [1,2,3,4]; a[1:3:5]`, RunError: fmt.Errorf("cap out of range")}, + {Script: `a = [1,2,3,4]; a[:2:-1]`, RunError: fmt.Errorf("cap out of range")}, + {Script: `a = [1,2,3,4]; a[:2:0]`, RunError: fmt.Errorf("cap out of range")}, + {Script: `a = [1,2,3,4]; a[:2:1]`, RunError: fmt.Errorf("cap out of range")}, + {Script: `a = [1,2,3,4]; a[:2:5]`, RunError: fmt.Errorf("cap out of range")}, + {Script: `a = [1,2,3,4]; a[1:3:"a"]`, RunError: fmt.Errorf("cap must be a number")}, + {Script: `a = [1,2,3,4]; a[1:3:1++]`, RunError: fmt.Errorf("invalid operation")}, + {Script: `a = "a"; b = a[0:1:1]`, RunError: fmt.Errorf("type string does not support cap")}, + {Script: `a = [1,2,3,4]; b = a[1:3:-1]`, RunError: fmt.Errorf("cap out of range")}, + {Script: `a = [1,2,3,4]; b = a[1:3:0]`, RunError: fmt.Errorf("cap out of range")}, + {Script: `a = [1,2,3,4]; b = a[1:3:2]`, RunError: fmt.Errorf("cap out of range")}, + {Script: `a = [1,2,3,4]; b = a[1:3:5]`, RunError: fmt.Errorf("cap out of range")}, + {Script: `a = [1,2,3,4]; b = a[1:3:"a"]`, RunError: fmt.Errorf("cap must be a number")}, + {Script: `a = [1,2,3,4]; b = a[1:3:1++]`, RunError: fmt.Errorf("invalid operation")}, + + // cap assigned errors + {Script: `a = [1,2,3,4]; a[1:1:-1] = 3`, RunError: fmt.Errorf("cap out of range")}, + {Script: `a = [1,2,3,4]; a[1:1:0] = 3`, RunError: fmt.Errorf("cap out of range")}, + {Script: `a = [1,2,3,4]; a[1:1:5] = 3`, RunError: fmt.Errorf("cap out of range")}, + {Script: `a = [1,2,3,4]; a[1:1:"a"] = 3`, RunError: fmt.Errorf("cap must be a number")}, + {Script: `a = [1,2,3,4]; a[1:3:1++] = 3`, RunError: fmt.Errorf("invalid operation")}, + + // cap + {Script: `a = [1,2,3,4]; a[1:3:3]`, RunOutput: []interface{}{int64(2), int64(3)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3), int64(4)}}}, + {Script: `a = [1,2,3,4]; a[1:3:4]`, RunOutput: []interface{}{int64(2), int64(3)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3), int64(4)}}}, + {Script: `a = [1,2,3,4]; a[:2:3]`, RunOutput: []interface{}{int64(1), int64(2)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3), int64(4)}}}, + {Script: `a = [1,2,3,4]; a[:2:4]`, RunOutput: []interface{}{int64(1), int64(2)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3), int64(4)}}}, + {Script: `a = [1,2,3,4]; b = a[1:3:3]`, RunOutput: []interface{}{int64(2), int64(3)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3), int64(4)}, "b": []interface{}{int64(2), int64(3)}}}, + {Script: `a = [1,2,3,4]; b = a[1:3:4]`, RunOutput: []interface{}{int64(2), int64(3)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3), int64(4)}, "b": []interface{}{int64(2), int64(3)}}}, + {Script: `a = [1,2,3,4]; b = a[:2:3]`, RunOutput: []interface{}{int64(1), int64(2)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3), int64(4)}, "b": []interface{}{int64(1), int64(2)}}}, + {Script: `a = [1,2,3,4]; b = a[:2:4]`, RunOutput: []interface{}{int64(1), int64(2)}, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3), int64(4)}, "b": []interface{}{int64(1), int64(2)}}}, + + // cap assigned + {Script: `a = [1,2,3,4]; a[1:1:1] = 3`, RunError: fmt.Errorf("slice cannot be assigned"), Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3), int64(4)}}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestSliceAppendSlices(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `a += 1`, Input: map[string]interface{}{"a": []bool{true}}, RunError: fmt.Errorf("invalid type conversion")}, + {Script: `a += 1.1`, Input: map[string]interface{}{"a": []bool{true}}, RunError: fmt.Errorf("invalid type conversion")}, + {Script: `a += "a"`, Input: map[string]interface{}{"a": []bool{true}}, RunError: fmt.Errorf("invalid type conversion")}, + {Script: `a += b`, Input: map[string]interface{}{"a": "a", "b": []bool{true}}, RunError: fmt.Errorf("invalid type conversion")}, + + {Script: `a += b`, Input: map[string]interface{}{"a": []bool{true}, "b": []string{"b"}}, RunError: fmt.Errorf("invalid type conversion")}, + {Script: `b += a`, Input: map[string]interface{}{"a": []bool{true}, "b": []string{"b"}}, RunError: fmt.Errorf("invalid type conversion")}, + + {Script: `a += nil`, Input: map[string]interface{}{"a": []bool{true}}, RunOutput: []bool{true, false}, Output: map[string]interface{}{"a": []bool{true, false}}}, + + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []bool{}}, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []bool{}, "b": []interface{}{}}}, + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []bool{true}}, RunOutput: []interface{}{true}, Output: map[string]interface{}{"a": []bool{true}, "b": []interface{}{true}}}, + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []bool{true, false}}, RunOutput: []interface{}{true, false}, Output: map[string]interface{}{"a": []bool{true, false}, "b": []interface{}{true, false}}}, + + {Script: `b = [true]; b += a`, Input: map[string]interface{}{"a": []bool{}}, RunOutput: []interface{}{true}, Output: map[string]interface{}{"a": []bool{}, "b": []interface{}{true}}}, + {Script: `b = [true]; b += a`, Input: map[string]interface{}{"a": []bool{true}}, RunOutput: []interface{}{true, true}, Output: map[string]interface{}{"a": []bool{true}, "b": []interface{}{true, true}}}, + {Script: `b = [true]; b += a`, Input: map[string]interface{}{"a": []bool{true, false}}, RunOutput: []interface{}{true, true, false}, Output: map[string]interface{}{"a": []bool{true, false}, "b": []interface{}{true, true, false}}}, + + {Script: `b = [true, false]; b += a`, Input: map[string]interface{}{"a": []bool{}}, RunOutput: []interface{}{true, false}, Output: map[string]interface{}{"a": []bool{}, "b": []interface{}{true, false}}}, + {Script: `b = [true, false]; b += a`, Input: map[string]interface{}{"a": []bool{true}}, RunOutput: []interface{}{true, false, true}, Output: map[string]interface{}{"a": []bool{true}, "b": []interface{}{true, false, true}}}, + {Script: `b = [true, false]; b += a`, Input: map[string]interface{}{"a": []bool{true, false}}, RunOutput: []interface{}{true, false, true, false}, Output: map[string]interface{}{"a": []bool{true, false}, "b": []interface{}{true, false, true, false}}}, + + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []int32{}}, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []int32{}, "b": []interface{}{}}}, + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []int32{1}}, RunOutput: []interface{}{int32(1)}, Output: map[string]interface{}{"a": []int32{1}, "b": []interface{}{int32(1)}}}, + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []int32{1, 2}}, RunOutput: []interface{}{int32(1), int32(2)}, Output: map[string]interface{}{"a": []int32{1, 2}, "b": []interface{}{int32(1), int32(2)}}}, + + {Script: `b = [1]; b += a`, Input: map[string]interface{}{"a": []int32{}}, RunOutput: []interface{}{int64(1)}, Output: map[string]interface{}{"a": []int32{}, "b": []interface{}{int64(1)}}}, + {Script: `b = [1]; b += a`, Input: map[string]interface{}{"a": []int32{1}}, RunOutput: []interface{}{int64(1), int32(1)}, Output: map[string]interface{}{"a": []int32{1}, "b": []interface{}{int64(1), int32(1)}}}, + {Script: `b = [1]; b += a`, Input: map[string]interface{}{"a": []int32{1, 2}}, RunOutput: []interface{}{int64(1), int32(1), int32(2)}, Output: map[string]interface{}{"a": []int32{1, 2}, "b": []interface{}{int64(1), int32(1), int32(2)}}}, + + {Script: `b = [1, 2]; b += a`, Input: map[string]interface{}{"a": []int32{}}, RunOutput: []interface{}{int64(1), int64(2)}, Output: map[string]interface{}{"a": []int32{}, "b": []interface{}{int64(1), int64(2)}}}, + {Script: `b = [1, 2]; b += a`, Input: map[string]interface{}{"a": []int32{1}}, RunOutput: []interface{}{int64(1), int64(2), int32(1)}, Output: map[string]interface{}{"a": []int32{1}, "b": []interface{}{int64(1), int64(2), int32(1)}}}, + {Script: `b = [1, 2]; b += a`, Input: map[string]interface{}{"a": []int32{1, 2}}, RunOutput: []interface{}{int64(1), int64(2), int32(1), int32(2)}, Output: map[string]interface{}{"a": []int32{1, 2}, "b": []interface{}{int64(1), int64(2), int32(1), int32(2)}}}, + + {Script: `b = [1.1]; b += a`, Input: map[string]interface{}{"a": []int32{}}, RunOutput: []interface{}{float64(1.1)}, Output: map[string]interface{}{"a": []int32{}, "b": []interface{}{float64(1.1)}}}, + {Script: `b = [1.1]; b += a`, Input: map[string]interface{}{"a": []int32{1}}, RunOutput: []interface{}{float64(1.1), int32(1)}, Output: map[string]interface{}{"a": []int32{1}, "b": []interface{}{float64(1.1), int32(1)}}}, + {Script: `b = [1.1]; b += a`, Input: map[string]interface{}{"a": []int32{1, 2}}, RunOutput: []interface{}{float64(1.1), int32(1), int32(2)}, Output: map[string]interface{}{"a": []int32{1, 2}, "b": []interface{}{float64(1.1), int32(1), int32(2)}}}, + + {Script: `b = [1.1, 2.2]; b += a`, Input: map[string]interface{}{"a": []int32{}}, RunOutput: []interface{}{float64(1.1), float64(2.2)}, Output: map[string]interface{}{"a": []int32{}, "b": []interface{}{float64(1.1), float64(2.2)}}}, + {Script: `b = [1.1, 2.2]; b += a`, Input: map[string]interface{}{"a": []int32{1}}, RunOutput: []interface{}{float64(1.1), float64(2.2), int32(1)}, Output: map[string]interface{}{"a": []int32{1}, "b": []interface{}{float64(1.1), float64(2.2), int32(1)}}}, + {Script: `b = [1.1, 2.2]; b += a`, Input: map[string]interface{}{"a": []int32{1, 2}}, RunOutput: []interface{}{float64(1.1), float64(2.2), int32(1), int32(2)}, Output: map[string]interface{}{"a": []int32{1, 2}, "b": []interface{}{float64(1.1), float64(2.2), int32(1), int32(2)}}}, + + {Script: `b = [1, 2.2]; b += a`, Input: map[string]interface{}{"a": []int32{}}, RunOutput: []interface{}{int64(1), float64(2.2)}, Output: map[string]interface{}{"a": []int32{}, "b": []interface{}{int64(1), float64(2.2)}}}, + {Script: `b = [1, 2.2]; b += a`, Input: map[string]interface{}{"a": []int32{1}}, RunOutput: []interface{}{int64(1), float64(2.2), int32(1)}, Output: map[string]interface{}{"a": []int32{1}, "b": []interface{}{int64(1), float64(2.2), int32(1)}}}, + {Script: `b = [1, 2.2]; b += a`, Input: map[string]interface{}{"a": []int32{1, 2}}, RunOutput: []interface{}{int64(1), float64(2.2), int32(1), int32(2)}, Output: map[string]interface{}{"a": []int32{1, 2}, "b": []interface{}{int64(1), float64(2.2), int32(1), int32(2)}}}, + + {Script: `b = [1.1, 2]; b += a`, Input: map[string]interface{}{"a": []int32{}}, RunOutput: []interface{}{float64(1.1), int64(2)}, Output: map[string]interface{}{"a": []int32{}, "b": []interface{}{float64(1.1), int64(2)}}}, + {Script: `b = [1.1, 2]; b += a`, Input: map[string]interface{}{"a": []int32{1}}, RunOutput: []interface{}{float64(1.1), int64(2), int32(1)}, Output: map[string]interface{}{"a": []int32{1}, "b": []interface{}{float64(1.1), int64(2), int32(1)}}}, + {Script: `b = [1.1, 2]; b += a`, Input: map[string]interface{}{"a": []int32{1, 2}}, RunOutput: []interface{}{float64(1.1), int64(2), int32(1), int32(2)}, Output: map[string]interface{}{"a": []int32{1, 2}, "b": []interface{}{float64(1.1), int64(2), int32(1), int32(2)}}}, + + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []int64{}}, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []int64{}, "b": []interface{}{}}}, + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []int64{1}}, RunOutput: []interface{}{int64(1)}, Output: map[string]interface{}{"a": []int64{1}, "b": []interface{}{int64(1)}}}, + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []int64{1, 2}}, RunOutput: []interface{}{int64(1), int64(2)}, Output: map[string]interface{}{"a": []int64{1, 2}, "b": []interface{}{int64(1), int64(2)}}}, + + {Script: `b = [1]; b += a`, Input: map[string]interface{}{"a": []int64{}}, RunOutput: []interface{}{int64(1)}, Output: map[string]interface{}{"a": []int64{}, "b": []interface{}{int64(1)}}}, + {Script: `b = [1]; b += a`, Input: map[string]interface{}{"a": []int64{1}}, RunOutput: []interface{}{int64(1), int64(1)}, Output: map[string]interface{}{"a": []int64{1}, "b": []interface{}{int64(1), int64(1)}}}, + {Script: `b = [1]; b += a`, Input: map[string]interface{}{"a": []int64{1, 2}}, RunOutput: []interface{}{int64(1), int64(1), int64(2)}, Output: map[string]interface{}{"a": []int64{1, 2}, "b": []interface{}{int64(1), int64(1), int64(2)}}}, + + {Script: `b = [1, 2]; b += a`, Input: map[string]interface{}{"a": []int64{}}, RunOutput: []interface{}{int64(1), int64(2)}, Output: map[string]interface{}{"a": []int64{}, "b": []interface{}{int64(1), int64(2)}}}, + {Script: `b = [1, 2]; b += a`, Input: map[string]interface{}{"a": []int64{1}}, RunOutput: []interface{}{int64(1), int64(2), int64(1)}, Output: map[string]interface{}{"a": []int64{1}, "b": []interface{}{int64(1), int64(2), int64(1)}}}, + {Script: `b = [1, 2]; b += a`, Input: map[string]interface{}{"a": []int64{1, 2}}, RunOutput: []interface{}{int64(1), int64(2), int64(1), int64(2)}, Output: map[string]interface{}{"a": []int64{1, 2}, "b": []interface{}{int64(1), int64(2), int64(1), int64(2)}}}, + + {Script: `b = [1.1]; b += a`, Input: map[string]interface{}{"a": []int64{}}, RunOutput: []interface{}{float64(1.1)}, Output: map[string]interface{}{"a": []int64{}, "b": []interface{}{float64(1.1)}}}, + {Script: `b = [1.1]; b += a`, Input: map[string]interface{}{"a": []int64{1}}, RunOutput: []interface{}{float64(1.1), int64(1)}, Output: map[string]interface{}{"a": []int64{1}, "b": []interface{}{float64(1.1), int64(1)}}}, + {Script: `b = [1.1]; b += a`, Input: map[string]interface{}{"a": []int64{1, 2}}, RunOutput: []interface{}{float64(1.1), int64(1), int64(2)}, Output: map[string]interface{}{"a": []int64{1, 2}, "b": []interface{}{float64(1.1), int64(1), int64(2)}}}, + + {Script: `b = [1.1, 2.2]; b += a`, Input: map[string]interface{}{"a": []int64{}}, RunOutput: []interface{}{float64(1.1), float64(2.2)}, Output: map[string]interface{}{"a": []int64{}, "b": []interface{}{float64(1.1), float64(2.2)}}}, + {Script: `b = [1.1, 2.2]; b += a`, Input: map[string]interface{}{"a": []int64{1}}, RunOutput: []interface{}{float64(1.1), float64(2.2), int64(1)}, Output: map[string]interface{}{"a": []int64{1}, "b": []interface{}{float64(1.1), float64(2.2), int64(1)}}}, + {Script: `b = [1.1, 2.2]; b += a`, Input: map[string]interface{}{"a": []int64{1, 2}}, RunOutput: []interface{}{float64(1.1), float64(2.2), int64(1), int64(2)}, Output: map[string]interface{}{"a": []int64{1, 2}, "b": []interface{}{float64(1.1), float64(2.2), int64(1), int64(2)}}}, + + {Script: `b = [1, 2.2]; b += a`, Input: map[string]interface{}{"a": []int64{}}, RunOutput: []interface{}{int64(1), float64(2.2)}, Output: map[string]interface{}{"a": []int64{}, "b": []interface{}{int64(1), float64(2.2)}}}, + {Script: `b = [1, 2.2]; b += a`, Input: map[string]interface{}{"a": []int64{1}}, RunOutput: []interface{}{int64(1), float64(2.2), int64(1)}, Output: map[string]interface{}{"a": []int64{1}, "b": []interface{}{int64(1), float64(2.2), int64(1)}}}, + {Script: `b = [1, 2.2]; b += a`, Input: map[string]interface{}{"a": []int64{1, 2}}, RunOutput: []interface{}{int64(1), float64(2.2), int64(1), int64(2)}, Output: map[string]interface{}{"a": []int64{1, 2}, "b": []interface{}{int64(1), float64(2.2), int64(1), int64(2)}}}, + + {Script: `b = [1.1, 2]; b += a`, Input: map[string]interface{}{"a": []int64{}}, RunOutput: []interface{}{float64(1.1), int64(2)}, Output: map[string]interface{}{"a": []int64{}, "b": []interface{}{float64(1.1), int64(2)}}}, + {Script: `b = [1.1, 2]; b += a`, Input: map[string]interface{}{"a": []int64{1}}, RunOutput: []interface{}{float64(1.1), int64(2), int64(1)}, Output: map[string]interface{}{"a": []int64{1}, "b": []interface{}{float64(1.1), int64(2), int64(1)}}}, + {Script: `b = [1.1, 2]; b += a`, Input: map[string]interface{}{"a": []int64{1, 2}}, RunOutput: []interface{}{float64(1.1), int64(2), int64(1), int64(2)}, Output: map[string]interface{}{"a": []int64{1, 2}, "b": []interface{}{float64(1.1), int64(2), int64(1), int64(2)}}}, + + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []float32{}}, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []float32{}, "b": []interface{}{}}}, + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []float32{1}}, RunOutput: []interface{}{float32(1)}, Output: map[string]interface{}{"a": []float32{1}, "b": []interface{}{float32(1)}}}, + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []float32{1, 2}}, RunOutput: []interface{}{float32(1), float32(2)}, Output: map[string]interface{}{"a": []float32{1, 2}, "b": []interface{}{float32(1), float32(2)}}}, + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []float32{1.1}}, RunOutput: []interface{}{float32(1.1)}, Output: map[string]interface{}{"a": []float32{1.1}, "b": []interface{}{float32(1.1)}}}, + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []float32{1.1, 2.2}}, RunOutput: []interface{}{float32(1.1), float32(2.2)}, Output: map[string]interface{}{"a": []float32{1.1, 2.2}, "b": []interface{}{float32(1.1), float32(2.2)}}}, + + {Script: `b = [1]; b += a`, Input: map[string]interface{}{"a": []float32{}}, RunOutput: []interface{}{int64(1)}, Output: map[string]interface{}{"a": []float32{}, "b": []interface{}{int64(1)}}}, + {Script: `b = [1]; b += a`, Input: map[string]interface{}{"a": []float32{1}}, RunOutput: []interface{}{int64(1), float32(1)}, Output: map[string]interface{}{"a": []float32{1}, "b": []interface{}{int64(1), float32(1)}}}, + {Script: `b = [1]; b += a`, Input: map[string]interface{}{"a": []float32{1, 2}}, RunOutput: []interface{}{int64(1), float32(1), float32(2)}, Output: map[string]interface{}{"a": []float32{1, 2}, "b": []interface{}{int64(1), float32(1), float32(2)}}}, + {Script: `b = [1]; b += a`, Input: map[string]interface{}{"a": []float32{1.1}}, RunOutput: []interface{}{int64(1), float32(1.1)}, Output: map[string]interface{}{"a": []float32{1.1}, "b": []interface{}{int64(1), float32(1.1)}}}, + {Script: `b = [1]; b += a`, Input: map[string]interface{}{"a": []float32{1.1, 2.2}}, RunOutput: []interface{}{int64(1), float32(1.1), float32(2.2)}, Output: map[string]interface{}{"a": []float32{1.1, 2.2}, "b": []interface{}{int64(1), float32(1.1), float32(2.2)}}}, + + {Script: `b = [1, 2]; b += a`, Input: map[string]interface{}{"a": []float32{}}, RunOutput: []interface{}{int64(1), int64(2)}, Output: map[string]interface{}{"a": []float32{}, "b": []interface{}{int64(1), int64(2)}}}, + {Script: `b = [1, 2]; b += a`, Input: map[string]interface{}{"a": []float32{1}}, RunOutput: []interface{}{int64(1), int64(2), float32(1)}, Output: map[string]interface{}{"a": []float32{1}, "b": []interface{}{int64(1), int64(2), float32(1)}}}, + {Script: `b = [1, 2]; b += a`, Input: map[string]interface{}{"a": []float32{1, 2}}, RunOutput: []interface{}{int64(1), int64(2), float32(1), float32(2)}, Output: map[string]interface{}{"a": []float32{1, 2}, "b": []interface{}{int64(1), int64(2), float32(1), float32(2)}}}, + {Script: `b = [1, 2]; b += a`, Input: map[string]interface{}{"a": []float32{1.1}}, RunOutput: []interface{}{int64(1), int64(2), float32(1.1)}, Output: map[string]interface{}{"a": []float32{1.1}, "b": []interface{}{int64(1), int64(2), float32(1.1)}}}, + {Script: `b = [1, 2]; b += a`, Input: map[string]interface{}{"a": []float32{1.1, 2.2}}, RunOutput: []interface{}{int64(1), int64(2), float32(1.1), float32(2.2)}, Output: map[string]interface{}{"a": []float32{1.1, 2.2}, "b": []interface{}{int64(1), int64(2), float32(1.1), float32(2.2)}}}, + + {Script: `b = [1.1]; b += a`, Input: map[string]interface{}{"a": []float32{}}, RunOutput: []interface{}{float64(1.1)}, Output: map[string]interface{}{"a": []float32{}, "b": []interface{}{float64(1.1)}}}, + {Script: `b = [1.1]; b += a`, Input: map[string]interface{}{"a": []float32{1}}, RunOutput: []interface{}{float64(1.1), float32(1)}, Output: map[string]interface{}{"a": []float32{1}, "b": []interface{}{float64(1.1), float32(1)}}}, + {Script: `b = [1.1]; b += a`, Input: map[string]interface{}{"a": []float32{1, 2}}, RunOutput: []interface{}{float64(1.1), float32(1), float32(2)}, Output: map[string]interface{}{"a": []float32{1, 2}, "b": []interface{}{float64(1.1), float32(1), float32(2)}}}, + {Script: `b = [1.1]; b += a`, Input: map[string]interface{}{"a": []float32{1.1}}, RunOutput: []interface{}{float64(1.1), float32(1.1)}, Output: map[string]interface{}{"a": []float32{1.1}, "b": []interface{}{float64(1.1), float32(1.1)}}}, + {Script: `b = [1.1]; b += a`, Input: map[string]interface{}{"a": []float32{1.1, 2.2}}, RunOutput: []interface{}{float64(1.1), float32(1.1), float32(2.2)}, Output: map[string]interface{}{"a": []float32{1.1, 2.2}, "b": []interface{}{float64(1.1), float32(1.1), float32(2.2)}}}, + + {Script: `b = [1.1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float32{}}, RunOutput: []interface{}{float64(1.1), float64(2.2)}, Output: map[string]interface{}{"a": []float32{}, "b": []interface{}{float64(1.1), float64(2.2)}}}, + {Script: `b = [1.1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float32{1}}, RunOutput: []interface{}{float64(1.1), float64(2.2), float32(1)}, Output: map[string]interface{}{"a": []float32{1}, "b": []interface{}{float64(1.1), float64(2.2), float32(1)}}}, + {Script: `b = [1.1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float32{1, 2}}, RunOutput: []interface{}{float64(1.1), float64(2.2), float32(1), float32(2)}, Output: map[string]interface{}{"a": []float32{1, 2}, "b": []interface{}{float64(1.1), float64(2.2), float32(1), float32(2)}}}, + {Script: `b = [1.1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float32{1.1}}, RunOutput: []interface{}{float64(1.1), float64(2.2), float32(1.1)}, Output: map[string]interface{}{"a": []float32{1.1}, "b": []interface{}{float64(1.1), float64(2.2), float32(1.1)}}}, + {Script: `b = [1.1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float32{1.1, 2.2}}, RunOutput: []interface{}{float64(1.1), float64(2.2), float32(1.1), float32(2.2)}, Output: map[string]interface{}{"a": []float32{1.1, 2.2}, "b": []interface{}{float64(1.1), float64(2.2), float32(1.1), float32(2.2)}}}, + + {Script: `b = [1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float32{}}, RunOutput: []interface{}{int64(1), float64(2.2)}, Output: map[string]interface{}{"a": []float32{}, "b": []interface{}{int64(1), float64(2.2)}}}, + {Script: `b = [1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float32{1}}, RunOutput: []interface{}{int64(1), float64(2.2), float32(1)}, Output: map[string]interface{}{"a": []float32{1}, "b": []interface{}{int64(1), float64(2.2), float32(1)}}}, + {Script: `b = [1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float32{1, 2}}, RunOutput: []interface{}{int64(1), float64(2.2), float32(1), float32(2)}, Output: map[string]interface{}{"a": []float32{1, 2}, "b": []interface{}{int64(1), float64(2.2), float32(1), float32(2)}}}, + {Script: `b = [1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float32{1.1}}, RunOutput: []interface{}{int64(1), float64(2.2), float32(1.1)}, Output: map[string]interface{}{"a": []float32{1.1}, "b": []interface{}{int64(1), float64(2.2), float32(1.1)}}}, + {Script: `b = [1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float32{1.1, 2.2}}, RunOutput: []interface{}{int64(1), float64(2.2), float32(1.1), float32(2.2)}, Output: map[string]interface{}{"a": []float32{1.1, 2.2}, "b": []interface{}{int64(1), float64(2.2), float32(1.1), float32(2.2)}}}, + + {Script: `b = [1.1, 2]; b += a`, Input: map[string]interface{}{"a": []float32{}}, RunOutput: []interface{}{float64(1.1), int64(2)}, Output: map[string]interface{}{"a": []float32{}, "b": []interface{}{float64(1.1), int64(2)}}}, + {Script: `b = [1.1, 2]; b += a`, Input: map[string]interface{}{"a": []float32{1}}, RunOutput: []interface{}{float64(1.1), int64(2), float32(1)}, Output: map[string]interface{}{"a": []float32{1}, "b": []interface{}{float64(1.1), int64(2), float32(1)}}}, + {Script: `b = [1.1, 2]; b += a`, Input: map[string]interface{}{"a": []float32{1, 2}}, RunOutput: []interface{}{float64(1.1), int64(2), float32(1), float32(2)}, Output: map[string]interface{}{"a": []float32{1, 2}, "b": []interface{}{float64(1.1), int64(2), float32(1), float32(2)}}}, + {Script: `b = [1.1, 2]; b += a`, Input: map[string]interface{}{"a": []float32{1.1}}, RunOutput: []interface{}{float64(1.1), int64(2), float32(1.1)}, Output: map[string]interface{}{"a": []float32{1.1}, "b": []interface{}{float64(1.1), int64(2), float32(1.1)}}}, + {Script: `b = [1.1, 2]; b += a`, Input: map[string]interface{}{"a": []float32{1.1, 2.2}}, RunOutput: []interface{}{float64(1.1), int64(2), float32(1.1), float32(2.2)}, Output: map[string]interface{}{"a": []float32{1.1, 2.2}, "b": []interface{}{float64(1.1), int64(2), float32(1.1), float32(2.2)}}}, + + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []float64{}}, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []float64{}, "b": []interface{}{}}}, + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []float64{1}}, RunOutput: []interface{}{float64(1)}, Output: map[string]interface{}{"a": []float64{1}, "b": []interface{}{float64(1)}}}, + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []float64{1, 2}}, RunOutput: []interface{}{float64(1), float64(2)}, Output: map[string]interface{}{"a": []float64{1, 2}, "b": []interface{}{float64(1), float64(2)}}}, + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []float64{1.1}}, RunOutput: []interface{}{float64(1.1)}, Output: map[string]interface{}{"a": []float64{1.1}, "b": []interface{}{float64(1.1)}}}, + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []float64{1.1, 2.2}}, RunOutput: []interface{}{float64(1.1), float64(2.2)}, Output: map[string]interface{}{"a": []float64{1.1, 2.2}, "b": []interface{}{float64(1.1), float64(2.2)}}}, + + {Script: `b = [1]; b += a`, Input: map[string]interface{}{"a": []float64{}}, RunOutput: []interface{}{int64(1)}, Output: map[string]interface{}{"a": []float64{}, "b": []interface{}{int64(1)}}}, + {Script: `b = [1]; b += a`, Input: map[string]interface{}{"a": []float64{1}}, RunOutput: []interface{}{int64(1), float64(1)}, Output: map[string]interface{}{"a": []float64{1}, "b": []interface{}{int64(1), float64(1)}}}, + {Script: `b = [1]; b += a`, Input: map[string]interface{}{"a": []float64{1, 2}}, RunOutput: []interface{}{int64(1), float64(1), float64(2)}, Output: map[string]interface{}{"a": []float64{1, 2}, "b": []interface{}{int64(1), float64(1), float64(2)}}}, + {Script: `b = [1]; b += a`, Input: map[string]interface{}{"a": []float64{1.1}}, RunOutput: []interface{}{int64(1), float64(1.1)}, Output: map[string]interface{}{"a": []float64{1.1}, "b": []interface{}{int64(1), float64(1.1)}}}, + {Script: `b = [1]; b += a`, Input: map[string]interface{}{"a": []float64{1.1, 2.2}}, RunOutput: []interface{}{int64(1), float64(1.1), float64(2.2)}, Output: map[string]interface{}{"a": []float64{1.1, 2.2}, "b": []interface{}{int64(1), float64(1.1), float64(2.2)}}}, + + {Script: `b = [1, 2]; b += a`, Input: map[string]interface{}{"a": []float64{}}, RunOutput: []interface{}{int64(1), int64(2)}, Output: map[string]interface{}{"a": []float64{}, "b": []interface{}{int64(1), int64(2)}}}, + {Script: `b = [1, 2]; b += a`, Input: map[string]interface{}{"a": []float64{1}}, RunOutput: []interface{}{int64(1), int64(2), float64(1)}, Output: map[string]interface{}{"a": []float64{1}, "b": []interface{}{int64(1), int64(2), float64(1)}}}, + {Script: `b = [1, 2]; b += a`, Input: map[string]interface{}{"a": []float64{1, 2}}, RunOutput: []interface{}{int64(1), int64(2), float64(1), float64(2)}, Output: map[string]interface{}{"a": []float64{1, 2}, "b": []interface{}{int64(1), int64(2), float64(1), float64(2)}}}, + {Script: `b = [1, 2]; b += a`, Input: map[string]interface{}{"a": []float64{1.1}}, RunOutput: []interface{}{int64(1), int64(2), float64(1.1)}, Output: map[string]interface{}{"a": []float64{1.1}, "b": []interface{}{int64(1), int64(2), float64(1.1)}}}, + {Script: `b = [1, 2]; b += a`, Input: map[string]interface{}{"a": []float64{1.1, 2.2}}, RunOutput: []interface{}{int64(1), int64(2), float64(1.1), float64(2.2)}, Output: map[string]interface{}{"a": []float64{1.1, 2.2}, "b": []interface{}{int64(1), int64(2), float64(1.1), float64(2.2)}}}, + + {Script: `b = [1.1]; b += a`, Input: map[string]interface{}{"a": []float64{}}, RunOutput: []interface{}{float64(1.1)}, Output: map[string]interface{}{"a": []float64{}, "b": []interface{}{float64(1.1)}}}, + {Script: `b = [1.1]; b += a`, Input: map[string]interface{}{"a": []float64{1}}, RunOutput: []interface{}{float64(1.1), float64(1)}, Output: map[string]interface{}{"a": []float64{1}, "b": []interface{}{float64(1.1), float64(1)}}}, + {Script: `b = [1.1]; b += a`, Input: map[string]interface{}{"a": []float64{1, 2}}, RunOutput: []interface{}{float64(1.1), float64(1), float64(2)}, Output: map[string]interface{}{"a": []float64{1, 2}, "b": []interface{}{float64(1.1), float64(1), float64(2)}}}, + {Script: `b = [1.1]; b += a`, Input: map[string]interface{}{"a": []float64{1.1}}, RunOutput: []interface{}{float64(1.1), float64(1.1)}, Output: map[string]interface{}{"a": []float64{1.1}, "b": []interface{}{float64(1.1), float64(1.1)}}}, + {Script: `b = [1.1]; b += a`, Input: map[string]interface{}{"a": []float64{1.1, 2.2}}, RunOutput: []interface{}{float64(1.1), float64(1.1), float64(2.2)}, Output: map[string]interface{}{"a": []float64{1.1, 2.2}, "b": []interface{}{float64(1.1), float64(1.1), float64(2.2)}}}, + + {Script: `b = [1.1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float64{}}, RunOutput: []interface{}{float64(1.1), float64(2.2)}, Output: map[string]interface{}{"a": []float64{}, "b": []interface{}{float64(1.1), float64(2.2)}}}, + {Script: `b = [1.1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float64{1}}, RunOutput: []interface{}{float64(1.1), float64(2.2), float64(1)}, Output: map[string]interface{}{"a": []float64{1}, "b": []interface{}{float64(1.1), float64(2.2), float64(1)}}}, + {Script: `b = [1.1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float64{1, 2}}, RunOutput: []interface{}{float64(1.1), float64(2.2), float64(1), float64(2)}, Output: map[string]interface{}{"a": []float64{1, 2}, "b": []interface{}{float64(1.1), float64(2.2), float64(1), float64(2)}}}, + {Script: `b = [1.1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float64{1.1}}, RunOutput: []interface{}{float64(1.1), float64(2.2), float64(1.1)}, Output: map[string]interface{}{"a": []float64{1.1}, "b": []interface{}{float64(1.1), float64(2.2), float64(1.1)}}}, + {Script: `b = [1.1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float64{1.1, 2.2}}, RunOutput: []interface{}{float64(1.1), float64(2.2), float64(1.1), float64(2.2)}, Output: map[string]interface{}{"a": []float64{1.1, 2.2}, "b": []interface{}{float64(1.1), float64(2.2), float64(1.1), float64(2.2)}}}, + + {Script: `b = [1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float64{}}, RunOutput: []interface{}{int64(1), float64(2.2)}, Output: map[string]interface{}{"a": []float64{}, "b": []interface{}{int64(1), float64(2.2)}}}, + {Script: `b = [1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float64{1}}, RunOutput: []interface{}{int64(1), float64(2.2), float64(1)}, Output: map[string]interface{}{"a": []float64{1}, "b": []interface{}{int64(1), float64(2.2), float64(1)}}}, + {Script: `b = [1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float64{1, 2}}, RunOutput: []interface{}{int64(1), float64(2.2), float64(1), float64(2)}, Output: map[string]interface{}{"a": []float64{1, 2}, "b": []interface{}{int64(1), float64(2.2), float64(1), float64(2)}}}, + {Script: `b = [1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float64{1.1}}, RunOutput: []interface{}{int64(1), float64(2.2), float64(1.1)}, Output: map[string]interface{}{"a": []float64{1.1}, "b": []interface{}{int64(1), float64(2.2), float64(1.1)}}}, + {Script: `b = [1, 2.2]; b += a`, Input: map[string]interface{}{"a": []float64{1.1, 2.2}}, RunOutput: []interface{}{int64(1), float64(2.2), float64(1.1), float64(2.2)}, Output: map[string]interface{}{"a": []float64{1.1, 2.2}, "b": []interface{}{int64(1), float64(2.2), float64(1.1), float64(2.2)}}}, + + {Script: `b = [1.1, 2]; b += a`, Input: map[string]interface{}{"a": []float64{}}, RunOutput: []interface{}{float64(1.1), int64(2)}, Output: map[string]interface{}{"a": []float64{}, "b": []interface{}{float64(1.1), int64(2)}}}, + {Script: `b = [1.1, 2]; b += a`, Input: map[string]interface{}{"a": []float64{1}}, RunOutput: []interface{}{float64(1.1), int64(2), float64(1)}, Output: map[string]interface{}{"a": []float64{1}, "b": []interface{}{float64(1.1), int64(2), float64(1)}}}, + {Script: `b = [1.1, 2]; b += a`, Input: map[string]interface{}{"a": []float64{1, 2}}, RunOutput: []interface{}{float64(1.1), int64(2), float64(1), float64(2)}, Output: map[string]interface{}{"a": []float64{1, 2}, "b": []interface{}{float64(1.1), int64(2), float64(1), float64(2)}}}, + {Script: `b = [1.1, 2]; b += a`, Input: map[string]interface{}{"a": []float64{1.1}}, RunOutput: []interface{}{float64(1.1), int64(2), float64(1.1)}, Output: map[string]interface{}{"a": []float64{1.1}, "b": []interface{}{float64(1.1), int64(2), float64(1.1)}}}, + {Script: `b = [1.1, 2]; b += a`, Input: map[string]interface{}{"a": []float64{1.1, 2.2}}, RunOutput: []interface{}{float64(1.1), int64(2), float64(1.1), float64(2.2)}, Output: map[string]interface{}{"a": []float64{1.1, 2.2}, "b": []interface{}{float64(1.1), int64(2), float64(1.1), float64(2.2)}}}, + + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []string{}}, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": []string{}, "b": []interface{}{}}}, + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []string{"a"}}, RunOutput: []interface{}{"a"}, Output: map[string]interface{}{"a": []string{"a"}, "b": []interface{}{"a"}}}, + {Script: `b = []; b += a`, Input: map[string]interface{}{"a": []string{"a", "b"}}, RunOutput: []interface{}{"a", "b"}, Output: map[string]interface{}{"a": []string{"a", "b"}, "b": []interface{}{"a", "b"}}}, + + {Script: `b = ["a"]; b += a`, Input: map[string]interface{}{"a": []string{}}, RunOutput: []interface{}{"a"}, Output: map[string]interface{}{"a": []string{}, "b": []interface{}{"a"}}}, + {Script: `b = ["a"]; b += a`, Input: map[string]interface{}{"a": []string{"a"}}, RunOutput: []interface{}{"a", "a"}, Output: map[string]interface{}{"a": []string{"a"}, "b": []interface{}{"a", "a"}}}, + {Script: `b = ["a"]; b += a`, Input: map[string]interface{}{"a": []string{"a", "b"}}, RunOutput: []interface{}{"a", "a", "b"}, Output: map[string]interface{}{"a": []string{"a", "b"}, "b": []interface{}{"a", "a", "b"}}}, + + {Script: `b = ["a", "b"]; b += a`, Input: map[string]interface{}{"a": []string{}}, RunOutput: []interface{}{"a", "b"}, Output: map[string]interface{}{"a": []string{}, "b": []interface{}{"a", "b"}}}, + {Script: `b = ["a", "b"]; b += a`, Input: map[string]interface{}{"a": []string{"a"}}, RunOutput: []interface{}{"a", "b", "a"}, Output: map[string]interface{}{"a": []string{"a"}, "b": []interface{}{"a", "b", "a"}}}, + {Script: `b = ["a", "b"]; b += a`, Input: map[string]interface{}{"a": []string{"a", "b"}}, RunOutput: []interface{}{"a", "b", "a", "b"}, Output: map[string]interface{}{"a": []string{"a", "b"}, "b": []interface{}{"a", "b", "a", "b"}}}, + + {Script: `b = [1, "a"]; b += a`, Input: map[string]interface{}{"a": []string{}}, RunOutput: []interface{}{int64(1), "a"}, Output: map[string]interface{}{"a": []string{}, "b": []interface{}{int64(1), "a"}}}, + {Script: `b = [1, "a"]; b += a`, Input: map[string]interface{}{"a": []string{"a"}}, RunOutput: []interface{}{int64(1), "a", "a"}, Output: map[string]interface{}{"a": []string{"a"}, "b": []interface{}{int64(1), "a", "a"}}}, + {Script: `b = [1, "a"]; b += a`, Input: map[string]interface{}{"a": []string{"a", "b"}}, RunOutput: []interface{}{int64(1), "a", "a", "b"}, Output: map[string]interface{}{"a": []string{"a", "b"}, "b": []interface{}{int64(1), "a", "a", "b"}}}, + + {Script: `a = []; a += [nil, nil]; a += [nil, nil]`, RunOutput: []interface{}{interface{}(nil), interface{}(nil), interface{}(nil), interface{}(nil)}, Output: map[string]interface{}{"a": []interface{}{interface{}(nil), interface{}(nil), interface{}(nil), interface{}(nil)}}}, + {Script: `a = []; a += [true, false]; a += [false, true]`, RunOutput: []interface{}{interface{}(true), interface{}(false), interface{}(false), interface{}(true)}, Output: map[string]interface{}{"a": []interface{}{interface{}(true), interface{}(false), interface{}(false), interface{}(true)}}}, + {Script: `a = []; a += [1, 2]; a += [3, 4]`, RunOutput: []interface{}{interface{}(int64(1)), interface{}(int64(2)), interface{}(int64(3)), interface{}(int64(4))}, Output: map[string]interface{}{"a": []interface{}{interface{}(int64(1)), interface{}(int64(2)), interface{}(int64(3)), interface{}(int64(4))}}}, + {Script: `a = []; a += [1.1, 2.2]; a += [3.3, 4.4]`, RunOutput: []interface{}{interface{}(float64(1.1)), interface{}(float64(2.2)), interface{}(float64(3.3)), interface{}(float64(4.4))}, Output: map[string]interface{}{"a": []interface{}{interface{}(float64(1.1)), interface{}(float64(2.2)), interface{}(float64(3.3)), interface{}(float64(4.4))}}}, + {Script: `a = []; a += ["a", "b"]; a += ["c", "d"]`, RunOutput: []interface{}{interface{}("a"), interface{}("b"), interface{}("c"), interface{}("d")}, Output: map[string]interface{}{"a": []interface{}{interface{}("a"), interface{}("b"), interface{}("c"), interface{}("d")}}}, + + {Script: `a = []; a += [[nil, nil]]; a += [[nil, nil]]`, RunOutput: []interface{}{[]interface{}{nil, nil}, []interface{}{nil, nil}}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{nil, nil}, []interface{}{nil, nil}}}}, + {Script: `a = []; a += [[true, false]]; a += [[false, true]]`, RunOutput: []interface{}{[]interface{}{true, false}, []interface{}{false, true}}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{true, false}, []interface{}{false, true}}}}, + {Script: `a = []; a += [[1, 2]]; a += [[3, 4]]`, RunOutput: []interface{}{[]interface{}{int64(1), int64(2)}, []interface{}{int64(3), int64(4)}}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{int64(1), int64(2)}, []interface{}{int64(3), int64(4)}}}}, + {Script: `a = []; a += [[1.1, 2.2]]; a += [[3.3, 4.4]]`, RunOutput: []interface{}{[]interface{}{float64(1.1), float64(2.2)}, []interface{}{float64(3.3), float64(4.4)}}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{float64(1.1), float64(2.2)}, []interface{}{float64(3.3), float64(4.4)}}}}, + {Script: `a = []; a += [["a", "b"]]; a += [["c", "d"]]`, RunOutput: []interface{}{[]interface{}{"a", "b"}, []interface{}{"c", "d"}}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{"a", "b"}, []interface{}{"c", "d"}}}}, + + {Script: `a = make([]bool); a += 1`, RunError: fmt.Errorf("invalid type conversion"), Output: map[string]interface{}{"a": []bool{}}}, + {Script: `a = make([]bool); a += true; a += 1`, RunError: fmt.Errorf("invalid type conversion"), Output: map[string]interface{}{"a": []bool{true}}}, + {Script: `a = make([]bool); a += [1]`, RunError: fmt.Errorf("invalid type conversion"), Output: map[string]interface{}{"a": []bool{}}}, + {Script: `a = make([]bool); a += [true, 1]`, RunError: fmt.Errorf("invalid type conversion"), Output: map[string]interface{}{"a": []bool{}}}, + + {Script: `a = make([]bool); a += true; a += false`, RunOutput: []bool{true, false}, Output: map[string]interface{}{"a": []bool{true, false}}}, + {Script: `a = make([]bool); a += [true]; a += [false]`, RunOutput: []bool{true, false}, Output: map[string]interface{}{"a": []bool{true, false}}}, + {Script: `a = make([]bool); a += [true, false]`, RunOutput: []bool{true, false}, Output: map[string]interface{}{"a": []bool{true, false}}}, + + {Script: `a = make([]bool); a += true; a += b`, Input: map[string]interface{}{"b": int64(1)}, RunError: fmt.Errorf("invalid type conversion"), Output: map[string]interface{}{"a": []bool{true}, "b": int64(1)}}, + {Script: `a = make([]bool); a += [true]; a += [b]`, Input: map[string]interface{}{"b": int64(1)}, RunError: fmt.Errorf("invalid type conversion"), Output: map[string]interface{}{"a": []bool{true}, "b": int64(1)}}, + {Script: `a = make([]bool); a += [true, b]`, Input: map[string]interface{}{"b": int64(1)}, RunError: fmt.Errorf("invalid type conversion"), Output: map[string]interface{}{"a": []bool{}, "b": int64(1)}}, + + {Script: `a = make([]bool); a += b; a += b`, Input: map[string]interface{}{"b": true}, RunOutput: []bool{true, true}, Output: map[string]interface{}{"a": []bool{true, true}, "b": true}}, + {Script: `a = make([]bool); a += [b]; a += [b]`, Input: map[string]interface{}{"b": true}, RunOutput: []bool{true, true}, Output: map[string]interface{}{"a": []bool{true, true}, "b": true}}, + {Script: `a = make([]bool); a += [b, b]`, Input: map[string]interface{}{"b": true}, RunOutput: []bool{true, true}, Output: map[string]interface{}{"a": []bool{true, true}, "b": true}}, + + {Script: `a = make([]bool); a += [true, false]; b = make([]int64); b += [1, 2]; a += b`, RunError: fmt.Errorf("invalid type conversion"), Output: map[string]interface{}{"a": []bool{true, false}, "b": []int64{int64(1), int64(2)}}}, + + {Script: `a = []; b = []; b += true; b += false; a += b`, RunOutput: []interface{}{true, false}, Output: map[string]interface{}{"a": []interface{}{true, false}, "b": []interface{}{true, false}}}, + {Script: `a = []; b = make([]bool); b += true; b += false; a += b`, RunOutput: []interface{}{true, false}, Output: map[string]interface{}{"a": []interface{}{true, false}, "b": []bool{true, false}}}, + {Script: `a = []; b = []; b += [true]; b += [false]; a += [b]`, RunOutput: []interface{}{[]interface{}{true, false}}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{true, false}}, "b": []interface{}{true, false}}}, + {Script: `a = []; b = make([]bool); b += [true]; b += [false]; a += [b]`, RunOutput: []interface{}{[]bool{true, false}}, Output: map[string]interface{}{"a": []interface{}{[]bool{true, false}}, "b": []bool{true, false}}}, + + {Script: `a = [true, false]; b = [true, false]; a += b`, RunOutput: []interface{}{true, false, true, false}, Output: map[string]interface{}{"a": []interface{}{true, false, true, false}, "b": []interface{}{true, false}}}, + {Script: `a = make([]bool); a += [true, false]; b = make([]bool); b += [true, false]; a += b`, RunOutput: []bool{true, false, true, false}, Output: map[string]interface{}{"a": []bool{true, false, true, false}, "b": []bool{true, false}}}, + {Script: `a = make([]bool); a += [true, false]; b = [true, false]; a += b`, RunOutput: []bool{true, false, true, false}, Output: map[string]interface{}{"a": []bool{true, false, true, false}, "b": []interface{}{true, false}}}, + {Script: `a = [true, false]; b = make([]bool); b += [true, false]; a += b`, RunOutput: []interface{}{true, false, true, false}, Output: map[string]interface{}{"a": []interface{}{true, false, true, false}, "b": []bool{true, false}}}, + + {Script: `a = make([][]bool); b = make([][]bool); a += b`, RunOutput: [][]bool{}, Output: map[string]interface{}{"a": [][]bool{}, "b": [][]bool{}}}, + {Script: `a = make([][]bool); b = make([][]bool); b += [[]]; a += b`, RunOutput: [][]bool{{}}, Output: map[string]interface{}{"a": [][]bool{{}}, "b": [][]bool{{}}}}, + {Script: `a = make([][]bool); a += [[]]; b = make([][]bool); a += b`, RunOutput: [][]bool{{}}, Output: map[string]interface{}{"a": [][]bool{{}}, "b": [][]bool{}}}, + {Script: `a = make([][]bool); a += [[]]; b = make([][]bool); b += [[]]; a += b`, RunOutput: [][]bool{{}, {}}, Output: map[string]interface{}{"a": [][]bool{{}, {}}, "b": [][]bool{{}}}}, + + {Script: `a = make([]bool); a += []; b = make([]bool); b += []; a += b`, RunOutput: []bool{}, Output: map[string]interface{}{"a": []bool{}, "b": []bool{}}}, + {Script: `a = make([]bool); a += [true]; b = make([]bool); b += []; a += b`, RunOutput: []bool{true}, Output: map[string]interface{}{"a": []bool{true}, "b": []bool{}}}, + {Script: `a = make([]bool); a += []; b = make([]bool); b += [true]; a += b`, RunOutput: []bool{true}, Output: map[string]interface{}{"a": []bool{true}, "b": []bool{true}}}, + {Script: `a = make([]bool); a += [true]; b = make([]bool); b += [true]; a += b`, RunOutput: []bool{true, true}, Output: map[string]interface{}{"a": []bool{true, true}, "b": []bool{true}}}, + + {Script: `a = make([][]bool); a += [true, false];`, RunError: fmt.Errorf("invalid type conversion"), Output: map[string]interface{}{"a": [][]bool{}}}, + {Script: `a = make([][]bool); a += [[true, false]]; b = make([]bool); b += [true, false]; a += b`, RunError: fmt.Errorf("invalid type conversion"), Output: map[string]interface{}{"a": [][]bool{{true, false}}, "b": []bool{true, false}}}, + {Script: `a = make([][]bool); a += [[true, false]]; b = [[1.1]]; a += b`, RunError: fmt.Errorf("invalid type conversion"), Output: map[string]interface{}{"a": [][]bool{{true, false}}, "b": []interface{}{[]interface{}{1.1}}}}, + {Script: `a = make([]bool); a += [true, false]; b = make([][]bool); b += [[true, false]]; a += b`, RunError: fmt.Errorf("invalid type conversion"), Output: map[string]interface{}{"a": []bool{true, false}, "b": [][]bool{{true, false}}}}, + + {Script: `a = make([][]interface); a += [[1, 2]]`, RunOutput: [][]interface{}{{int64(1), int64(2)}}, Output: map[string]interface{}{"a": [][]interface{}{{int64(1), int64(2)}}}}, + {Script: `a = make([][]interface); b = [1, 2]; a += [b]`, RunOutput: [][]interface{}{{int64(1), int64(2)}}, Output: map[string]interface{}{"a": [][]interface{}{{int64(1), int64(2)}}, "b": []interface{}{int64(1), int64(2)}}}, + {Script: `a = make([][]interface); a += [[1, 2],[3, 4]]`, RunOutput: [][]interface{}{{int64(1), int64(2)}, {int64(3), int64(4)}}, Output: map[string]interface{}{"a": [][]interface{}{{int64(1), int64(2)}, {int64(3), int64(4)}}}}, + {Script: `a = make([][]interface); b = [1, 2]; a += [b]; b = [3, 4]; a += [b]`, RunOutput: [][]interface{}{{int64(1), int64(2)}, {int64(3), int64(4)}}, Output: map[string]interface{}{"a": [][]interface{}{{int64(1), int64(2)}, {int64(3), int64(4)}}, "b": []interface{}{int64(3), int64(4)}}}, + + {Script: `a = [["a", "b"], ["c", "d"]]; b = [["w", "x"], ["y", "z"]]; a += b`, RunOutput: []interface{}{[]interface{}{"a", "b"}, []interface{}{"c", "d"}, []interface{}{"w", "x"}, []interface{}{"y", "z"}}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{"a", "b"}, []interface{}{"c", "d"}, []interface{}{"w", "x"}, []interface{}{"y", "z"}}, "b": []interface{}{[]interface{}{"w", "x"}, []interface{}{"y", "z"}}}}, + {Script: `a = make([][]string); a += [["a", "b"], ["c", "d"]]; b = make([][]string); b += [["w", "x"], ["y", "z"]]; a += b`, RunOutput: [][]string{{"a", "b"}, {"c", "d"}, {"w", "x"}, {"y", "z"}}, Output: map[string]interface{}{"a": [][]string{{"a", "b"}, {"c", "d"}, {"w", "x"}, {"y", "z"}}, "b": [][]string{{"w", "x"}, {"y", "z"}}}}, + {Script: `a = make([][]string); a += [["a", "b"], ["c", "d"]]; b = [["w", "x"], ["y", "z"]]; a += b`, RunOutput: [][]string{{"a", "b"}, {"c", "d"}, {"w", "x"}, {"y", "z"}}, Output: map[string]interface{}{"a": [][]string{{"a", "b"}, {"c", "d"}, {"w", "x"}, {"y", "z"}}, "b": []interface{}{[]interface{}{"w", "x"}, []interface{}{"y", "z"}}}}, + {Script: `a = [["a", "b"], ["c", "d"]]; b = make([][]string); b += [["w", "x"], ["y", "z"]]; a += b`, RunOutput: []interface{}{[]interface{}{"a", "b"}, []interface{}{"c", "d"}, []string{"w", "x"}, []string{"y", "z"}}, Output: map[string]interface{}{"a": []interface{}{[]interface{}{"a", "b"}, []interface{}{"c", "d"}, []string{"w", "x"}, []string{"y", "z"}}, "b": [][]string{{"w", "x"}, {"y", "z"}}}}, + + {Script: `a = make([][]int64); a += [[1, 2], [3, 4]]; b = make([][]int32); b += [[5, 6], [7, 8]]; a += b`, RunOutput: [][]int64{{int64(1), int64(2)}, {int64(3), int64(4)}, {int64(5), int64(6)}, {int64(7), int64(8)}}, Output: map[string]interface{}{"a": [][]int64{{int64(1), int64(2)}, {int64(3), int64(4)}, {int64(5), int64(6)}, {int64(7), int64(8)}}, "b": [][]int32{{int32(5), int32(6)}, {int32(7), int32(8)}}}}, + {Script: `a = make([][]int32); a += [[1, 2], [3, 4]]; b = make([][]int64); b += [[5, 6], [7, 8]]; a += b`, RunOutput: [][]int32{{int32(1), int32(2)}, {int32(3), int32(4)}, {int32(5), int32(6)}, {int32(7), int32(8)}}, Output: map[string]interface{}{"a": [][]int32{{int32(1), int32(2)}, {int32(3), int32(4)}, {int32(5), int32(6)}, {int32(7), int32(8)}}, "b": [][]int64{{int64(5), int64(6)}, {int64(7), int64(8)}}}}, + {Script: `a = make([][]int64); a += [[1, 2], [3, 4]]; b = make([][]float64); b += [[5, 6], [7, 8]]; a += b`, RunOutput: [][]int64{{int64(1), int64(2)}, {int64(3), int64(4)}, {int64(5), int64(6)}, {int64(7), int64(8)}}, Output: map[string]interface{}{"a": [][]int64{{int64(1), int64(2)}, {int64(3), int64(4)}, {int64(5), int64(6)}, {int64(7), int64(8)}}, "b": [][]float64{{float64(5), float64(6)}, {float64(7), float64(8)}}}}, + {Script: `a = make([][]float64); a += [[1, 2], [3, 4]]; b = make([][]int64); b += [[5, 6], [7, 8]]; a += b`, RunOutput: [][]float64{{float64(1), float64(2)}, {float64(3), float64(4)}, {float64(5), float64(6)}, {float64(7), float64(8)}}, Output: map[string]interface{}{"a": [][]float64{{float64(1), float64(2)}, {float64(3), float64(4)}, {float64(5), float64(6)}, {float64(7), float64(8)}}, "b": [][]int64{{int64(5), int64(6)}, {int64(7), int64(8)}}}}, + + {Script: `a = make([][][]interface); a += [[[1, 2]]]`, RunOutput: [][][]interface{}{{{int64(1), int64(2)}}}, Output: map[string]interface{}{"a": [][][]interface{}{{{int64(1), int64(2)}}}}}, + {Script: `a = make([][][]interface); b = [[1, 2]]; a += [b]`, RunOutput: [][][]interface{}{{{int64(1), int64(2)}}}, Output: map[string]interface{}{"a": [][][]interface{}{{{int64(1), int64(2)}}}, "b": []interface{}{[]interface{}{int64(1), int64(2)}}}}, + {Script: `a = make([][][]interface); b = [1, 2]; a += [[b]]`, RunOutput: [][][]interface{}{{{int64(1), int64(2)}}}, Output: map[string]interface{}{"a": [][][]interface{}{{{int64(1), int64(2)}}}, "b": []interface{}{int64(1), int64(2)}}}, + + {Script: `a = make([][][]interface); a += [[[1, 2],[3, 4]]]`, RunOutput: [][][]interface{}{{{int64(1), int64(2)}, {int64(3), int64(4)}}}, Output: map[string]interface{}{"a": [][][]interface{}{{{int64(1), int64(2)}, {int64(3), int64(4)}}}}}, + {Script: `a = make([][][]interface); b = [[1, 2],[3, 4]]; a += [b]`, RunOutput: [][][]interface{}{{{int64(1), int64(2)}, {int64(3), int64(4)}}}, Output: map[string]interface{}{"a": [][][]interface{}{{{int64(1), int64(2)}, {int64(3), int64(4)}}}, "b": []interface{}{[]interface{}{int64(1), int64(2)}, []interface{}{int64(3), int64(4)}}}}, + {Script: `a = make([][][]interface); b = [1, 2]; c = [b]; b = [3, 4]; c += [b]; a += [c]`, RunOutput: [][][]interface{}{{{int64(1), int64(2)}, {int64(3), int64(4)}}}, Output: map[string]interface{}{"a": [][][]interface{}{{{int64(1), int64(2)}, {int64(3), int64(4)}}}, "b": []interface{}{int64(3), int64(4)}, "c": []interface{}{[]interface{}{int64(1), int64(2)}, []interface{}{int64(3), int64(4)}}}}, + {Script: `a = make([][][]interface); b = [1, 2]; c = []; c += [b]; b = [3, 4]; c += [b]; a += [c]`, RunOutput: [][][]interface{}{{{int64(1), int64(2)}, {int64(3), int64(4)}}}, Output: map[string]interface{}{"a": [][][]interface{}{{{int64(1), int64(2)}, {int64(3), int64(4)}}}, "b": []interface{}{int64(3), int64(4)}, "c": []interface{}{[]interface{}{int64(1), int64(2)}, []interface{}{int64(3), int64(4)}}}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestMaps(t *testing.T) { + t.Parallel() + + tests := []Test{ + // TOFIX: this should have a ParseError + {Script: `{ , }`, RunOutput: map[interface{}]interface{}{}}, + {Script: `{"a": }`, ParseError: fmt.Errorf("syntax error")}, + {Script: `{ :"a"}`, ParseError: fmt.Errorf("syntax error")}, + {Script: `{ , "b":"b"}`, ParseError: fmt.Errorf("syntax error: unexpected ','"), RunOutput: map[interface{}]interface{}{"b": "b"}}, + {Script: `{"a":"a", , "c":"c"}`, ParseError: fmt.Errorf("syntax error")}, + {Script: `{"b": 1++}`, RunError: fmt.Errorf("invalid operation")}, + {Script: `{1++: 1}`, RunError: fmt.Errorf("invalid operation")}, + {Script: `a = {}; a.b.c`, RunError: fmt.Errorf("type interface does not support member operation")}, + {Script: `a = {}; a.b.c = 1`, RunError: fmt.Errorf("type interface does not support member operation")}, + {Script: `a = {}; a[1++]`, RunError: fmt.Errorf("invalid operation")}, + {Script: `a = {}; a[1++] = 1`, RunError: fmt.Errorf("invalid operation")}, + {Script: `b[1]`, RunError: fmt.Errorf("undefined symbol 'b'")}, + {Script: `b[1] = 1`, RunError: fmt.Errorf("undefined symbol 'b'")}, + {Script: `z.y.x = 1`, RunError: fmt.Errorf("undefined symbol 'z'")}, + + {Script: `{}`, RunOutput: map[interface{}]interface{}{}}, + {Script: `{"b": nil}`, RunOutput: map[interface{}]interface{}{"b": nil}}, + {Script: `{"b": true}`, RunOutput: map[interface{}]interface{}{"b": true}}, + {Script: `{"b": 1}`, RunOutput: map[interface{}]interface{}{"b": int64(1)}}, + {Script: `{"b": 1.1}`, RunOutput: map[interface{}]interface{}{"b": float64(1.1)}}, + {Script: `{"b": "b"}`, RunOutput: map[interface{}]interface{}{"b": "b"}}, + + {Script: `{1: nil}`, RunOutput: map[interface{}]interface{}{int64(1): nil}}, + {Script: `{1: true}`, RunOutput: map[interface{}]interface{}{int64(1): true}}, + {Script: `{1: 2}`, RunOutput: map[interface{}]interface{}{int64(1): int64(2)}}, + {Script: `{1: 2.2}`, RunOutput: map[interface{}]interface{}{int64(1): float64(2.2)}}, + {Script: `{1: "b"}`, RunOutput: map[interface{}]interface{}{int64(1): "b"}}, + + {Script: `a = {}`, RunOutput: map[interface{}]interface{}{}, Output: map[string]interface{}{"a": map[interface{}]interface{}{}}}, + {Script: `a = map{}`, RunOutput: map[interface{}]interface{}{}, Output: map[string]interface{}{"a": map[interface{}]interface{}{}}}, + {Script: `a = map {}`, RunOutput: map[interface{}]interface{}{}, Output: map[string]interface{}{"a": map[interface{}]interface{}{}}}, + {Script: `a = {"b": nil}`, RunOutput: map[interface{}]interface{}{"b": nil}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": nil}}}, + {Script: `a = {"b": true}`, RunOutput: map[interface{}]interface{}{"b": true}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": true}}}, + {Script: `a = {"b": 1}`, RunOutput: map[interface{}]interface{}{"b": int64(1)}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1)}}}, + {Script: `a = {"b": 1.1}`, RunOutput: map[interface{}]interface{}{"b": float64(1.1)}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": float64(1.1)}}}, + {Script: `a = {"b": "b"}`, RunOutput: map[interface{}]interface{}{"b": "b"}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": "b"}}}, + + {Script: `a = {"b": {}}`, RunOutput: map[interface{}]interface{}{"b": map[interface{}]interface{}{}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": map[interface{}]interface{}{}}}}, + {Script: `a = {"b": {"c": nil}}`, RunOutput: map[interface{}]interface{}{"b": map[interface{}]interface{}{"c": nil}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": map[interface{}]interface{}{"c": nil}}}}, + {Script: `a = {"b": {"c": true}}`, RunOutput: map[interface{}]interface{}{"b": map[interface{}]interface{}{"c": true}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": map[interface{}]interface{}{"c": true}}}}, + {Script: `a = {"b": {"c": 1}}`, RunOutput: map[interface{}]interface{}{"b": map[interface{}]interface{}{"c": int64(1)}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": map[interface{}]interface{}{"c": int64(1)}}}}, + {Script: `a = {"b": {"c": 1.1}}`, RunOutput: map[interface{}]interface{}{"b": map[interface{}]interface{}{"c": float64(1.1)}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": map[interface{}]interface{}{"c": float64(1.1)}}}}, + {Script: `a = {"b": {"c": "c"}}`, RunOutput: map[interface{}]interface{}{"b": map[interface{}]interface{}{"c": "c"}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": map[interface{}]interface{}{"c": "c"}}}}, + + {Script: `a = {"b": {}}; a.b`, RunOutput: map[interface{}]interface{}{}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": map[interface{}]interface{}{}}}}, + {Script: `a = {"b": {"c": nil}}; a.b`, RunOutput: map[interface{}]interface{}{"c": nil}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": map[interface{}]interface{}{"c": nil}}}}, + {Script: `a = {"b": {"c": true}}; a.b`, RunOutput: map[interface{}]interface{}{"c": true}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": map[interface{}]interface{}{"c": true}}}}, + {Script: `a = {"b": {"c": 1}}; a.b`, RunOutput: map[interface{}]interface{}{"c": int64(1)}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": map[interface{}]interface{}{"c": int64(1)}}}}, + {Script: `a = {"b": {"c": 1.1}}; a.b`, RunOutput: map[interface{}]interface{}{"c": float64(1.1)}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": map[interface{}]interface{}{"c": float64(1.1)}}}}, + {Script: `a = {"b": {"c": "c"}}; a.b`, RunOutput: map[interface{}]interface{}{"c": "c"}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": map[interface{}]interface{}{"c": "c"}}}}, + + {Script: `a = {"b": []}`, RunOutput: map[interface{}]interface{}{"b": []interface{}{}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{}}}}, + {Script: `a = {"b": [nil]}`, RunOutput: map[interface{}]interface{}{"b": []interface{}{nil}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{nil}}}}, + {Script: `a = {"b": [true]}`, RunOutput: map[interface{}]interface{}{"b": []interface{}{true}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{true}}}}, + {Script: `a = {"b": [1]}`, RunOutput: map[interface{}]interface{}{"b": []interface{}{int64(1)}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{int64(1)}}}}, + {Script: `a = {"b": [1.1]}`, RunOutput: map[interface{}]interface{}{"b": []interface{}{float64(1.1)}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{float64(1.1)}}}}, + {Script: `a = {"b": ["c"]}`, RunOutput: map[interface{}]interface{}{"b": []interface{}{"c"}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{"c"}}}}, + + {Script: `a = {}; a.b`, RunOutput: nil, Output: map[string]interface{}{"a": map[interface{}]interface{}{}}}, + {Script: `a = {"b": nil}; a.b`, RunOutput: nil, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": nil}}}, + {Script: `a = {"b": true}; a.b`, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": true}}}, + {Script: `a = {"b": 1}; a.b`, RunOutput: int64(1), Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1)}}}, + {Script: `a = {"b": 1.1}; a.b`, RunOutput: float64(1.1), Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": float64(1.1)}}}, + {Script: `a = {"b": "b"}; a.b`, RunOutput: "b", Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": "b"}}}, + + {Script: `a = {}; a["b"]`, RunOutput: nil, Output: map[string]interface{}{"a": map[interface{}]interface{}{}}}, + {Script: `a = {"b": nil}; a["b"]`, RunOutput: nil, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": nil}}}, + {Script: `a = {"b": true}; a["b"]`, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": true}}}, + {Script: `a = {"b": 1}; a["b"]`, RunOutput: int64(1), Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1)}}}, + {Script: `a = {"b": 1.1}; a["b"]`, RunOutput: float64(1.1), Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": float64(1.1)}}}, + {Script: `a = {"b": "b"}; a["b"]`, RunOutput: "b", Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": "b"}}}, + + {Script: `a`, Input: map[string]interface{}{"a": map[string]interface{}{}}, RunOutput: map[string]interface{}{}, Output: map[string]interface{}{"a": map[string]interface{}{}}}, + {Script: `a`, Input: map[string]interface{}{"a": map[string]interface{}{"b": nil}}, RunOutput: map[string]interface{}{"b": nil}, Output: map[string]interface{}{"a": map[string]interface{}{"b": nil}}}, + {Script: `a`, Input: map[string]interface{}{"a": map[string]interface{}{"b": true}}, RunOutput: map[string]interface{}{"b": true}, Output: map[string]interface{}{"a": map[string]interface{}{"b": true}}}, + {Script: `a`, Input: map[string]interface{}{"a": map[string]interface{}{"b": int32(1)}}, RunOutput: map[string]interface{}{"b": int32(1)}, Output: map[string]interface{}{"a": map[string]interface{}{"b": int32(1)}}}, + {Script: `a`, Input: map[string]interface{}{"a": map[string]interface{}{"b": int64(1)}}, RunOutput: map[string]interface{}{"b": int64(1)}, Output: map[string]interface{}{"a": map[string]interface{}{"b": int64(1)}}}, + {Script: `a`, Input: map[string]interface{}{"a": map[string]interface{}{"b": float32(1.1)}}, RunOutput: map[string]interface{}{"b": float32(1.1)}, Output: map[string]interface{}{"a": map[string]interface{}{"b": float32(1.1)}}}, + {Script: `a`, Input: map[string]interface{}{"a": map[string]interface{}{"b": float64(1.1)}}, RunOutput: map[string]interface{}{"b": float64(1.1)}, Output: map[string]interface{}{"a": map[string]interface{}{"b": float64(1.1)}}}, + {Script: `a`, Input: map[string]interface{}{"a": map[string]interface{}{"b": "b"}}, RunOutput: map[string]interface{}{"b": "b"}, Output: map[string]interface{}{"a": map[string]interface{}{"b": "b"}}}, + + {Script: `a.b`, Input: map[string]interface{}{"a": map[string]interface{}{}}, RunOutput: nil, Output: map[string]interface{}{"a": map[string]interface{}{}}}, + {Script: `a.b`, Input: map[string]interface{}{"a": map[string]interface{}{"b": nil}}, RunOutput: nil, Output: map[string]interface{}{"a": map[string]interface{}{"b": nil}}}, + {Script: `a.b`, Input: map[string]interface{}{"a": map[string]interface{}{"b": true}}, RunOutput: true, Output: map[string]interface{}{"a": map[string]interface{}{"b": true}}}, + {Script: `a.b`, Input: map[string]interface{}{"a": map[string]interface{}{"b": int32(1)}}, RunOutput: int32(1), Output: map[string]interface{}{"a": map[string]interface{}{"b": int32(1)}}}, + {Script: `a.b`, Input: map[string]interface{}{"a": map[string]interface{}{"b": int64(1)}}, RunOutput: int64(1), Output: map[string]interface{}{"a": map[string]interface{}{"b": int64(1)}}}, + {Script: `a.b`, Input: map[string]interface{}{"a": map[string]interface{}{"b": float32(1.1)}}, RunOutput: float32(1.1), Output: map[string]interface{}{"a": map[string]interface{}{"b": float32(1.1)}}}, + {Script: `a.b`, Input: map[string]interface{}{"a": map[string]interface{}{"b": float64(1.1)}}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": map[string]interface{}{"b": float64(1.1)}}}, + {Script: `a.b`, Input: map[string]interface{}{"a": map[string]interface{}{"b": "b"}}, RunOutput: "b", Output: map[string]interface{}{"a": map[string]interface{}{"b": "b"}}}, + + {Script: `a.b`, Input: map[string]interface{}{"a": map[string]bool{"a": false, "b": true}}, RunOutput: true, Output: map[string]interface{}{"a": map[string]bool{"a": false, "b": true}}}, + {Script: `a.b`, Input: map[string]interface{}{"a": map[string]int32{"a": int32(1), "b": int32(2)}}, RunOutput: int32(2), Output: map[string]interface{}{"a": map[string]int32{"a": int32(1), "b": int32(2)}}}, + {Script: `a.b`, Input: map[string]interface{}{"a": map[string]int64{"a": int64(1), "b": int64(2)}}, RunOutput: int64(2), Output: map[string]interface{}{"a": map[string]int64{"a": int64(1), "b": int64(2)}}}, + {Script: `a.b`, Input: map[string]interface{}{"a": map[string]float32{"a": float32(1.1), "b": float32(2.2)}}, RunOutput: float32(2.2), Output: map[string]interface{}{"a": map[string]float32{"a": float32(1.1), "b": float32(2.2)}}}, + {Script: `a.b`, Input: map[string]interface{}{"a": map[string]float64{"a": float64(1.1), "b": float64(2.2)}}, RunOutput: float64(2.2), Output: map[string]interface{}{"a": map[string]float64{"a": float64(1.1), "b": float64(2.2)}}}, + {Script: `a.b`, Input: map[string]interface{}{"a": map[string]string{"a": "a", "b": "b"}}, RunOutput: "b", Output: map[string]interface{}{"a": map[string]string{"a": "a", "b": "b"}}}, + + {Script: `a["b"]`, Input: map[string]interface{}{"a": map[string]interface{}{}}, RunOutput: nil, Output: map[string]interface{}{"a": map[string]interface{}{}}}, + {Script: `a["b"]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": reflect.Value{}}}, RunOutput: reflect.Value{}, Output: map[string]interface{}{"a": map[string]interface{}{"b": reflect.Value{}}}}, + {Script: `a["b"]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": nil}}, RunOutput: nil, Output: map[string]interface{}{"a": map[string]interface{}{"b": nil}}}, + {Script: `a["b"]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": true}}, RunOutput: true, Output: map[string]interface{}{"a": map[string]interface{}{"b": true}}}, + {Script: `a["b"]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": int32(1)}}, RunOutput: int32(1), Output: map[string]interface{}{"a": map[string]interface{}{"b": int32(1)}}}, + {Script: `a["b"]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": int64(1)}}, RunOutput: int64(1), Output: map[string]interface{}{"a": map[string]interface{}{"b": int64(1)}}}, + {Script: `a["b"]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": float32(1.1)}}, RunOutput: float32(1.1), Output: map[string]interface{}{"a": map[string]interface{}{"b": float32(1.1)}}}, + {Script: `a["b"]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": float64(1.1)}}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": map[string]interface{}{"b": float64(1.1)}}}, + {Script: `a["b"]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": "b"}}, RunOutput: "b", Output: map[string]interface{}{"a": map[string]interface{}{"b": "b"}}}, + + {Script: `a[0:1]`, Input: map[string]interface{}{"a": map[string]interface{}{}}, RunError: fmt.Errorf("type map does not support slice operation"), Output: map[string]interface{}{"a": map[string]interface{}{}}}, + {Script: `a[0:1]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": reflect.Value{}}}, RunError: fmt.Errorf("type map does not support slice operation"), Output: map[string]interface{}{"a": map[string]interface{}{"b": reflect.Value{}}}}, + {Script: `a[0:1]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": nil}}, RunError: fmt.Errorf("type map does not support slice operation"), Output: map[string]interface{}{"a": map[string]interface{}{"b": nil}}}, + {Script: `a[0:1]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": true}}, RunError: fmt.Errorf("type map does not support slice operation"), Output: map[string]interface{}{"a": map[string]interface{}{"b": true}}}, + {Script: `a[0:1]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": int32(1)}}, RunError: fmt.Errorf("type map does not support slice operation"), Output: map[string]interface{}{"a": map[string]interface{}{"b": int32(1)}}}, + {Script: `a[0:1]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": int64(1)}}, RunError: fmt.Errorf("type map does not support slice operation"), Output: map[string]interface{}{"a": map[string]interface{}{"b": int64(1)}}}, + {Script: `a[0:1]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": float32(1.1)}}, RunError: fmt.Errorf("type map does not support slice operation"), Output: map[string]interface{}{"a": map[string]interface{}{"b": float32(1.1)}}}, + {Script: `a[0:1]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": float64(1.1)}}, RunError: fmt.Errorf("type map does not support slice operation"), Output: map[string]interface{}{"a": map[string]interface{}{"b": float64(1.1)}}}, + {Script: `a[0:1]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": "b"}}, RunError: fmt.Errorf("type map does not support slice operation"), Output: map[string]interface{}{"a": map[string]interface{}{"b": "b"}}}, + + {Script: `a[c]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": "b"}, "c": nil}, RunOutput: nil, Output: map[string]interface{}{"a": map[string]interface{}{"b": "b"}, "c": nil}}, + {Script: `a[c]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": "b"}, "c": true}, RunOutput: nil, Output: map[string]interface{}{"a": map[string]interface{}{"b": "b"}, "c": true}}, + {Script: `a[c]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": "b"}, "c": int32(1)}, RunOutput: nil, Output: map[string]interface{}{"a": map[string]interface{}{"b": "b"}, "c": int32(1)}}, + {Script: `a[c]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": "b"}, "c": int64(1)}, RunOutput: nil, Output: map[string]interface{}{"a": map[string]interface{}{"b": "b"}, "c": int64(1)}}, + {Script: `a[c]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": "b"}, "c": float32(1.1)}, RunOutput: nil, Output: map[string]interface{}{"a": map[string]interface{}{"b": "b"}, "c": float32(1.1)}}, + {Script: `a[c]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": "b"}, "c": float64(1.1)}, RunOutput: nil, Output: map[string]interface{}{"a": map[string]interface{}{"b": "b"}, "c": float64(1.1)}}, + {Script: `a[c]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": "b"}, "c": "b"}, RunOutput: "b", Output: map[string]interface{}{"a": map[string]interface{}{"b": "b"}, "c": "b"}}, + {Script: `a[c]`, Input: map[string]interface{}{"a": map[string]interface{}{"b": "b"}, "c": "c"}, RunOutput: nil, Output: map[string]interface{}{"a": map[string]interface{}{"b": "b"}, "c": "c"}}, + + {Script: `a.b = nil`, Input: map[string]interface{}{"a": map[string]interface{}{}}, RunOutput: nil, Output: map[string]interface{}{"a": map[string]interface{}{"b": nil}}}, + {Script: `a.b = true`, Input: map[string]interface{}{"a": map[string]interface{}{}}, RunOutput: true, Output: map[string]interface{}{"a": map[string]interface{}{"b": true}}}, + {Script: `a.b = 1`, Input: map[string]interface{}{"a": map[string]interface{}{}}, RunOutput: int64(1), Output: map[string]interface{}{"a": map[string]interface{}{"b": int64(1)}}}, + {Script: `a.b = 1.1`, Input: map[string]interface{}{"a": map[string]interface{}{}}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": map[string]interface{}{"b": float64(1.1)}}}, + {Script: `a.b = "b"`, Input: map[string]interface{}{"a": map[string]interface{}{}}, RunOutput: "b", Output: map[string]interface{}{"a": map[string]interface{}{"b": "b"}}}, + + {Script: `a.b = true`, Input: map[string]interface{}{"a": map[string]bool{"a": true, "b": false}}, RunOutput: true, Output: map[string]interface{}{"a": map[string]bool{"a": true, "b": true}}}, + {Script: `a.b = 3`, Input: map[string]interface{}{"a": map[string]int32{"a": int32(1), "b": int32(2)}}, RunOutput: int64(3), Output: map[string]interface{}{"a": map[string]int32{"a": int32(1), "b": int32(3)}}}, + {Script: `a.b = 3`, Input: map[string]interface{}{"a": map[string]int64{"a": int64(1), "b": int64(2)}}, RunOutput: int64(3), Output: map[string]interface{}{"a": map[string]int64{"a": int64(1), "b": int64(3)}}}, + {Script: `a.b = 3.3`, Input: map[string]interface{}{"a": map[string]float32{"a": float32(1.1), "b": float32(2.2)}}, RunOutput: float64(3.3), Output: map[string]interface{}{"a": map[string]float32{"a": float32(1.1), "b": float32(3.3)}}}, + {Script: `a.b = 3.3`, Input: map[string]interface{}{"a": map[string]float64{"a": float64(1.1), "b": float64(2.2)}}, RunOutput: float64(3.3), Output: map[string]interface{}{"a": map[string]float64{"a": float64(1.1), "b": float64(3.3)}}}, + {Script: `a.b = "c"`, Input: map[string]interface{}{"a": map[string]string{"a": "a", "b": "b"}}, RunOutput: "c", Output: map[string]interface{}{"a": map[string]string{"a": "a", "b": "c"}}}, + + {Script: `a["b"] = true`, Input: map[string]interface{}{"a": map[string]bool{"a": true, "b": false}}, RunOutput: true, Output: map[string]interface{}{"a": map[string]bool{"a": true, "b": true}}}, + {Script: `a["b"] = 3`, Input: map[string]interface{}{"a": map[string]int32{"a": int32(1), "b": int32(2)}}, RunOutput: int64(3), Output: map[string]interface{}{"a": map[string]int32{"a": int32(1), "b": int32(3)}}}, + {Script: `a["b"] = 3`, Input: map[string]interface{}{"a": map[string]int64{"a": int64(1), "b": int64(2)}}, RunOutput: int64(3), Output: map[string]interface{}{"a": map[string]int64{"a": int64(1), "b": int64(3)}}}, + {Script: `a["b"] = 3.3`, Input: map[string]interface{}{"a": map[string]float32{"a": float32(1.1), "b": float32(2.2)}}, RunOutput: float64(3.3), Output: map[string]interface{}{"a": map[string]float32{"a": float32(1.1), "b": float32(3.3)}}}, + {Script: `a["b"] = 3.3`, Input: map[string]interface{}{"a": map[string]float64{"a": float64(1.1), "b": float64(2.2)}}, RunOutput: float64(3.3), Output: map[string]interface{}{"a": map[string]float64{"a": float64(1.1), "b": float64(3.3)}}}, + {Script: `a["b"] = "c"`, Input: map[string]interface{}{"a": map[string]string{"a": "a", "b": "b"}}, RunOutput: "c", Output: map[string]interface{}{"a": map[string]string{"a": "a", "b": "c"}}}, + + {Script: `a[c] = "x"`, Input: map[string]interface{}{"a": map[string]interface{}{"b": "b"}, "c": true}, RunError: fmt.Errorf("index type bool cannot be used for map index type string"), RunOutput: nil, Output: map[string]interface{}{"a": map[string]interface{}{"b": "b"}, "c": true}}, + {Script: `a[c] = "x"`, Input: map[string]interface{}{"a": map[bool]interface{}{true: "b"}, "c": true}, RunOutput: "x", Output: map[string]interface{}{"a": map[bool]interface{}{true: "x"}, "c": true}}, + + // note if passed an uninitialized map there does not seem to be a way to update that map + {Script: `a`, Input: map[string]interface{}{"a": testMapEmpty}, RunOutput: testMapEmpty, Output: map[string]interface{}{"a": testMapEmpty}}, + {Script: `a.b`, Input: map[string]interface{}{"a": testMapEmpty}, RunOutput: nil, Output: map[string]interface{}{"a": testMapEmpty}}, + {Script: `a.b = 1`, Input: map[string]interface{}{"a": testMapEmpty}, RunOutput: int64(1), Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1)}}}, + {Script: `a.b`, Input: map[string]interface{}{"a": testMapEmpty}, RunOutput: nil, Output: map[string]interface{}{"a": testMapEmpty}}, + {Script: `a["b"]`, Input: map[string]interface{}{"a": testMapEmpty}, RunOutput: nil, Output: map[string]interface{}{"a": testMapEmpty}}, + {Script: `a["b"] = 1`, Input: map[string]interface{}{"a": testMapEmpty}, RunOutput: int64(1), Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1)}}}, + {Script: `a["b"]`, Input: map[string]interface{}{"a": testMapEmpty}, RunOutput: nil, Output: map[string]interface{}{"a": testMapEmpty}}, + + {Script: `a`, Input: map[string]interface{}{"a": testMap}, RunOutput: testMap, Output: map[string]interface{}{"a": testMap}}, + {Script: `a.a`, Input: map[string]interface{}{"a": testMap}, RunOutput: nil, Output: map[string]interface{}{"a": testMap}}, + {Script: `a.a = true`, Input: map[string]interface{}{"a": testMap}, RunOutput: true, Output: map[string]interface{}{"a": testMap}}, + {Script: `a.a`, Input: map[string]interface{}{"a": testMap}, RunOutput: true, Output: map[string]interface{}{"a": testMap}}, + {Script: `a.a = nil`, Input: map[string]interface{}{"a": testMap}, RunOutput: nil, Output: map[string]interface{}{"a": testMap}}, + + {Script: `a`, Input: map[string]interface{}{"a": testMap}, RunOutput: testMap, Output: map[string]interface{}{"a": testMap}}, + {Script: `a.b`, Input: map[string]interface{}{"a": testMap}, RunOutput: true, Output: map[string]interface{}{"a": testMap}}, + {Script: `a.b = false`, Input: map[string]interface{}{"a": testMap}, RunOutput: false, Output: map[string]interface{}{"a": testMap}}, + {Script: `a.b`, Input: map[string]interface{}{"a": testMap}, RunOutput: false, Output: map[string]interface{}{"a": testMap}}, + {Script: `a.b = true`, Input: map[string]interface{}{"a": testMap}, RunOutput: true, Output: map[string]interface{}{"a": testMap}}, + + {Script: `a`, Input: map[string]interface{}{"a": testMap}, RunOutput: testMap, Output: map[string]interface{}{"a": testMap}}, + {Script: `a.c`, Input: map[string]interface{}{"a": testMap}, RunOutput: int64(1), Output: map[string]interface{}{"a": testMap}}, + {Script: `a.c = 2`, Input: map[string]interface{}{"a": testMap}, RunOutput: int64(2), Output: map[string]interface{}{"a": testMap}}, + {Script: `a.c`, Input: map[string]interface{}{"a": testMap}, RunOutput: int64(2), Output: map[string]interface{}{"a": testMap}}, + {Script: `a.c = 1`, Input: map[string]interface{}{"a": testMap}, RunOutput: int64(1), Output: map[string]interface{}{"a": testMap}}, + + {Script: `a`, Input: map[string]interface{}{"a": testMap}, RunOutput: testMap, Output: map[string]interface{}{"a": testMap}}, + {Script: `a.d`, Input: map[string]interface{}{"a": testMap}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": testMap}}, + {Script: `a.d = 2.2`, Input: map[string]interface{}{"a": testMap}, RunOutput: float64(2.2), Output: map[string]interface{}{"a": testMap}}, + {Script: `a.d`, Input: map[string]interface{}{"a": testMap}, RunOutput: float64(2.2), Output: map[string]interface{}{"a": testMap}}, + {Script: `a.d = 1.1`, Input: map[string]interface{}{"a": testMap}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": testMap}}, + + {Script: `a`, Input: map[string]interface{}{"a": testMap}, RunOutput: testMap, Output: map[string]interface{}{"a": testMap}}, + {Script: `a.e`, Input: map[string]interface{}{"a": testMap}, RunOutput: "e", Output: map[string]interface{}{"a": testMap}}, + {Script: `a.e = "x"`, Input: map[string]interface{}{"a": testMap}, RunOutput: "x", Output: map[string]interface{}{"a": testMap}}, + {Script: `a.e`, Input: map[string]interface{}{"a": testMap}, RunOutput: "x", Output: map[string]interface{}{"a": testMap}}, + {Script: `a.e = "e"`, Input: map[string]interface{}{"a": testMap}, RunOutput: "e", Output: map[string]interface{}{"a": testMap}}, + + {Script: `a = {"b": 1, "c": nil}`, RunOutput: map[interface{}]interface{}{"b": int64(1), "c": nil}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + {Script: `a = {"b": 1, "c": nil}; a.b`, RunOutput: int64(1), Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + {Script: `a = {"b": 1, "c": nil}; a.c`, RunOutput: nil, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + {Script: `a = {"b": 1, "c": nil}; a.d`, RunOutput: nil, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + + {Script: `a = {"b": 1, "c": nil}; a == nil`, RunOutput: false, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + {Script: `a = {"b": 1, "c": nil}; a != nil`, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + {Script: `a = {"b": 1, "c": nil}; a.b == nil`, RunOutput: false, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + {Script: `a = {"b": 1, "c": nil}; a.b != nil`, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + {Script: `a = {"b": 1, "c": nil}; a.c == nil`, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + {Script: `a = {"b": 1, "c": nil}; a.c != nil`, RunOutput: false, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + {Script: `a = {"b": 1, "c": nil}; a.d == nil`, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + {Script: `a = {"b": 1, "c": nil}; a.d != nil`, RunOutput: false, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + + {Script: `a = {"b": 1, "c": nil}; a == 1`, RunOutput: false, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + {Script: `a = {"b": 1, "c": nil}; a != 1`, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + {Script: `a = {"b": 1, "c": nil}; a.b == 1`, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + {Script: `a = {"b": 1, "c": nil}; a.b != 1`, RunOutput: false, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + {Script: `a = {"b": 1, "c": nil}; a.c == 1`, RunOutput: false, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + {Script: `a = {"b": 1, "c": nil}; a.c != 1`, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + {Script: `a = {"b": 1, "c": nil}; a.d == 1`, RunOutput: false, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + {Script: `a = {"b": 1, "c": nil}; a.d != 1`, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1), "c": nil}}}, + + {Script: `a["b"]`, Input: map[string]interface{}{"a": map[interface{}]bool{"b": true}}, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]bool{"b": true}}}, + {Script: `a["b"]`, Input: map[string]interface{}{"a": map[interface{}]int32{"b": int32(1)}}, RunOutput: int32(1), Output: map[string]interface{}{"a": map[interface{}]int32{"b": int32(1)}}}, + {Script: `a["b"]`, Input: map[string]interface{}{"a": map[interface{}]int64{"b": int64(1)}}, RunOutput: int64(1), Output: map[string]interface{}{"a": map[interface{}]int64{"b": int64(1)}}}, + {Script: `a["b"]`, Input: map[string]interface{}{"a": map[interface{}]float32{"b": float32(1.1)}}, RunOutput: float32(1.1), Output: map[string]interface{}{"a": map[interface{}]float32{"b": float32(1.1)}}}, + {Script: `a["b"]`, Input: map[string]interface{}{"a": map[interface{}]float64{"b": float64(1.1)}}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": map[interface{}]float64{"b": float64(1.1)}}}, + {Script: `a["b"]`, Input: map[string]interface{}{"a": map[interface{}]string{"b": "b"}}, RunOutput: "b", Output: map[string]interface{}{"a": map[interface{}]string{"b": "b"}}}, + + {Script: `a.b`, Input: map[string]interface{}{"a": map[interface{}]bool{"b": true}}, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]bool{"b": true}}}, + {Script: `a.b`, Input: map[string]interface{}{"a": map[interface{}]int32{"b": int32(1)}}, RunOutput: int32(1), Output: map[string]interface{}{"a": map[interface{}]int32{"b": int32(1)}}}, + {Script: `a.b`, Input: map[string]interface{}{"a": map[interface{}]int64{"b": int64(1)}}, RunOutput: int64(1), Output: map[string]interface{}{"a": map[interface{}]int64{"b": int64(1)}}}, + {Script: `a.b`, Input: map[string]interface{}{"a": map[interface{}]float32{"b": float32(1.1)}}, RunOutput: float32(1.1), Output: map[string]interface{}{"a": map[interface{}]float32{"b": float32(1.1)}}}, + {Script: `a.b`, Input: map[string]interface{}{"a": map[interface{}]float64{"b": float64(1.1)}}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": map[interface{}]float64{"b": float64(1.1)}}}, + {Script: `a.b`, Input: map[string]interface{}{"a": map[interface{}]string{"b": "b"}}, RunOutput: "b", Output: map[string]interface{}{"a": map[interface{}]string{"b": "b"}}}, + + {Script: `a = {}; a[true] = true`, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]interface{}{true: true}}}, + {Script: `a = {}; a[1] = 1`, RunOutput: int64(1), Output: map[string]interface{}{"a": map[interface{}]interface{}{int64(1): int64(1)}}}, + {Script: `a = {}; a[1.1] = 1.1`, RunOutput: float64(1.1), Output: map[string]interface{}{"a": map[interface{}]interface{}{float64(1.1): float64(1.1)}}}, + + {Script: `a = {}; a[true] = true; a[true]`, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]interface{}{true: true}}}, + {Script: `a = {}; a[1] = 1; a[1]`, RunOutput: int64(1), Output: map[string]interface{}{"a": map[interface{}]interface{}{int64(1): int64(1)}}}, + {Script: `a = {}; a[1.1] = 1.1; a[1.1]`, RunOutput: float64(1.1), Output: map[string]interface{}{"a": map[interface{}]interface{}{float64(1.1): float64(1.1)}}}, + + {Script: `a = {}; a[b] = b`, Input: map[string]interface{}{"b": nil}, RunOutput: nil, Output: map[string]interface{}{"a": map[interface{}]interface{}{nil: nil}}}, + {Script: `a = {}; a[b] = b`, Input: map[string]interface{}{"b": int32(1)}, RunOutput: int32(1), Output: map[string]interface{}{"a": map[interface{}]interface{}{int32(1): int32(1)}}}, + {Script: `a = {}; a[b] = b`, Input: map[string]interface{}{"b": int64(1)}, RunOutput: int64(1), Output: map[string]interface{}{"a": map[interface{}]interface{}{int64(1): int64(1)}}}, + {Script: `a = {}; a[b] = b`, Input: map[string]interface{}{"b": float32(1.1)}, RunOutput: float32(1.1), Output: map[string]interface{}{"a": map[interface{}]interface{}{float32(1.1): float32(1.1)}}}, + {Script: `a = {}; a[b] = b`, Input: map[string]interface{}{"b": float64(1.1)}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": map[interface{}]interface{}{float64(1.1): float64(1.1)}}}, + {Script: `a = {}; a[b] = b`, Input: map[string]interface{}{"b": "b"}, RunOutput: "b", Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": "b"}}}, + + {Script: `a = {}; a[b] = b; a[b]`, Input: map[string]interface{}{"b": nil}, RunOutput: nil, Output: map[string]interface{}{"a": map[interface{}]interface{}{nil: nil}}}, + {Script: `a = {}; a[b] = b; a[b]`, Input: map[string]interface{}{"b": int32(1)}, RunOutput: int32(1), Output: map[string]interface{}{"a": map[interface{}]interface{}{int32(1): int32(1)}}}, + {Script: `a = {}; a[b] = b; a[b]`, Input: map[string]interface{}{"b": int64(1)}, RunOutput: int64(1), Output: map[string]interface{}{"a": map[interface{}]interface{}{int64(1): int64(1)}}}, + {Script: `a = {}; a[b] = b; a[b]`, Input: map[string]interface{}{"b": float32(1.1)}, RunOutput: float32(1.1), Output: map[string]interface{}{"a": map[interface{}]interface{}{float32(1.1): float32(1.1)}}}, + {Script: `a = {}; a[b] = b; a[b]`, Input: map[string]interface{}{"b": float64(1.1)}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": map[interface{}]interface{}{float64(1.1): float64(1.1)}}}, + {Script: `a = {}; a[b] = b; a[b]`, Input: map[string]interface{}{"b": "b"}, RunOutput: "b", Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": "b"}}}, + + // test equal nil when not found + {Script: `a = {"b":"b"}; if a.c == nil { return 1 }`, RunOutput: int64(1), Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": "b"}}}, + {Script: `a = {"b":"b"}; if a["c"] == nil { return 1 }`, RunOutput: int64(1), Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": "b"}}}, + + // test map create with spacing and comma + {Script: `{"b": "b",}`, RunOutput: map[interface{}]interface{}{"b": "b"}}, + {Script: `{"b": "b" +}`, RunOutput: map[interface{}]interface{}{"b": "b"}}, + {Script: `{"b": "b", +}`, RunOutput: map[interface{}]interface{}{"b": "b"}}, + {Script: `{"b": "b", "c": "c"}`, RunOutput: map[interface{}]interface{}{"b": "b", "c": "c"}}, + {Script: `{"b": "b", "c": "c",}`, RunOutput: map[interface{}]interface{}{"b": "b", "c": "c"}}, + {Script: `{"b": "b", "c": "c" +}`, RunOutput: map[interface{}]interface{}{"b": "b", "c": "c"}}, + {Script: `{"b": "b", "c": "c", +}`, RunOutput: map[interface{}]interface{}{"b": "b", "c": "c"}}, + {Script: `{"b": "b", +"c": "c"}`, RunOutput: map[interface{}]interface{}{"b": "b", "c": "c"}}, + {Script: `{"b": "b", +"c": "c",}`, RunOutput: map[interface{}]interface{}{"b": "b", "c": "c"}}, + {Script: `{"b": "b", +"c": "c" +}`, RunOutput: map[interface{}]interface{}{"b": "b", "c": "c"}}, + {Script: `{"b": "b", +"c": "c", +}`, RunOutput: map[interface{}]interface{}{"b": "b", "c": "c"}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestExistenceOfKeyInMaps(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `a = {"b":"b"}; v, ok = a[1++]`, RunError: fmt.Errorf("invalid operation")}, + {Script: `a = {"b":"b"}; b.c, ok = a["b"]`, RunError: fmt.Errorf("undefined symbol 'b'")}, + {Script: `a = {"b":"b"}; v, b.c = a["b"]`, RunError: fmt.Errorf("undefined symbol 'b'")}, + + {Script: `a = {"b":"b"}; v, ok = a["a"]`, RunOutput: nil, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": "b"}, "v": nil, "ok": false}}, + {Script: `a = {"b":"b"}; v, ok = a["b"]`, RunOutput: "b", Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": "b"}, "v": "b", "ok": true}}, + {Script: `a = {"b":"b", "c":"c"}; v, ok = a["a"]`, RunOutput: nil, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": "b", "c": "c"}, "v": nil, "ok": false}}, + {Script: `a = {"b":"b", "c":"c"}; v, ok = a["b"]`, RunOutput: "b", Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": "b", "c": "c"}, "v": "b", "ok": true}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestDeleteMaps(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `delete(1++, "b")`, RunError: fmt.Errorf("invalid operation")}, + {Script: `delete({}, 1++)`, RunError: fmt.Errorf("invalid operation")}, + {Script: `delete(nil, "b")`, RunError: fmt.Errorf("first argument to delete cannot be type interface")}, + {Script: `delete(1, "b")`, RunError: fmt.Errorf("first argument to delete cannot be type int64")}, + + {Script: `delete(a, "")`, Input: map[string]interface{}{"a": testMapEmpty}, Output: map[string]interface{}{"a": testMapEmpty}}, + {Script: `delete(a, "")`, Input: map[string]interface{}{"a": map[string]interface{}{"b": "b"}}, Output: map[string]interface{}{"a": map[string]interface{}{"b": "b"}}}, + {Script: `delete(a, "a")`, Input: map[string]interface{}{"a": map[string]interface{}{"b": "b"}}, Output: map[string]interface{}{"a": map[string]interface{}{"b": "b"}}}, + {Script: `delete(a, "b")`, Input: map[string]interface{}{"a": map[string]interface{}{"b": "b"}}, Output: map[string]interface{}{"a": map[string]interface{}{}}}, + {Script: `delete(a, "a")`, Input: map[string]interface{}{"a": map[string]interface{}{"b": "b", "c": "c"}}, Output: map[string]interface{}{"a": map[string]interface{}{"b": "b", "c": "c"}}}, + {Script: `delete(a, "b")`, Input: map[string]interface{}{"a": map[string]interface{}{"b": "b", "c": "c"}}, Output: map[string]interface{}{"a": map[string]interface{}{"c": "c"}}}, + + {Script: `delete(a, 0)`, Input: map[string]interface{}{"a": map[int64]interface{}{1: 1}}, Output: map[string]interface{}{"a": map[int64]interface{}{1: 1}}}, + {Script: `delete(a, 1)`, Input: map[string]interface{}{"a": map[int64]interface{}{1: 1}}, Output: map[string]interface{}{"a": map[int64]interface{}{}}}, + {Script: `delete(a, 0)`, Input: map[string]interface{}{"a": map[int64]interface{}{1: 1, 2: 2}}, Output: map[string]interface{}{"a": map[int64]interface{}{1: 1, 2: 2}}}, + {Script: `delete(a, 1)`, Input: map[string]interface{}{"a": map[int64]interface{}{1: 1, 2: 2}}, Output: map[string]interface{}{"a": map[int64]interface{}{2: 2}}}, + + {Script: `delete({}, "")`}, + {Script: `delete({}, 1)`}, + {Script: `delete({}, "a")`}, + {Script: `delete({"b":"b"}, "")`}, + {Script: `delete({"b":"b"}, 1)`}, + {Script: `delete({"b":"b"}, "a")`}, + {Script: `delete({"b":"b"}, "b")`}, + + {Script: `a = {"b": "b"}; delete(a, "a")`, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": "b"}}}, + {Script: `a = {"b": "b"}; delete(a, "b")`, Output: map[string]interface{}{"a": map[interface{}]interface{}{}}}, + {Script: `a = {"b": "b", "c":"c"}; delete(a, "a")`, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": "b", "c": "c"}}}, + {Script: `a = {"b": "b", "c":"c"}; delete(a, "b")`, Output: map[string]interface{}{"a": map[interface{}]interface{}{"c": "c"}}}, + + {Script: `a = {"b": ["b"]}; delete(a, "a")`, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{"b"}}}}, + {Script: `a = {"b": ["b"]}; delete(a, "b")`, Output: map[string]interface{}{"a": map[interface{}]interface{}{}}}, + {Script: `a = {"b": ["b"], "c": ["c"]}; delete(a, "a")`, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{"b"}, "c": []interface{}{"c"}}}}, + {Script: `a = {"b": ["b"], "c": ["c"]}; delete(a, "b")`, Output: map[string]interface{}{"a": map[interface{}]interface{}{"c": []interface{}{"c"}}}}, + + {Script: `a = {"b": ["b"]}; b = &a; delete(*b, "a")`, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{"b"}}}}, + {Script: `a = {"b": ["b"]}; b = &a; delete(*b, "b")`, Output: map[string]interface{}{"a": map[interface{}]interface{}{}}}, + {Script: `a = {"b": ["b"], "c": ["c"]}; b = &a; delete(*b, "a")`, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{"b"}, "c": []interface{}{"c"}}}}, + {Script: `a = {"b": ["b"], "c": ["c"]}; b = &a; delete(*b, "b")`, Output: map[string]interface{}{"a": map[interface{}]interface{}{"c": []interface{}{"c"}}}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestMakeMaps(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `map[[]string]string {"a":"a"}`, RunError: fmt.Errorf("reflect.MapOf: invalid key type []string")}, + } + runTests(t, tests, nil, &Options{Debug: false}) + + tests = []Test{ + {Script: `make(mapStringBool)`, Types: map[string]interface{}{"mapStringBool": map[string]bool{}}, RunOutput: map[string]bool{}}, + {Script: `make(mapStringInt32)`, Types: map[string]interface{}{"mapStringInt32": map[string]int32{}}, RunOutput: map[string]int32{}}, + {Script: `make(mapStringInt64)`, Types: map[string]interface{}{"mapStringInt64": map[string]int64{}}, RunOutput: map[string]int64{}}, + {Script: `make(mapStringFloat32)`, Types: map[string]interface{}{"mapStringFloat32": map[string]float32{}}, RunOutput: map[string]float32{}}, + {Script: `make(mapStringFloat64)`, Types: map[string]interface{}{"mapStringFloat64": map[string]float64{}}, RunOutput: map[string]float64{}}, + {Script: `make(mapStringString)`, Types: map[string]interface{}{"mapStringString": map[string]string{}}, RunOutput: map[string]string{}}, + + {Script: `a = make(mapStringBool)`, Types: map[string]interface{}{"mapStringBool": map[string]bool{}}, RunOutput: map[string]bool{}, Output: map[string]interface{}{"a": map[string]bool{}}}, + {Script: `a = make(mapStringInt32)`, Types: map[string]interface{}{"mapStringInt32": map[string]int32{}}, RunOutput: map[string]int32{}, Output: map[string]interface{}{"a": map[string]int32{}}}, + {Script: `a = make(mapStringInt64)`, Types: map[string]interface{}{"mapStringInt64": map[string]int64{}}, RunOutput: map[string]int64{}, Output: map[string]interface{}{"a": map[string]int64{}}}, + {Script: `a = make(mapStringFloat32)`, Types: map[string]interface{}{"mapStringFloat32": map[string]float32{}}, RunOutput: map[string]float32{}, Output: map[string]interface{}{"a": map[string]float32{}}}, + {Script: `a = make(mapStringFloat64)`, Types: map[string]interface{}{"mapStringFloat64": map[string]float64{}}, RunOutput: map[string]float64{}, Output: map[string]interface{}{"a": map[string]float64{}}}, + {Script: `a = make(mapStringString)`, Types: map[string]interface{}{"mapStringString": map[string]string{}}, RunOutput: map[string]string{}, Output: map[string]interface{}{"a": map[string]string{}}}, + + {Script: `a = make(mapStringBool); a["b"] = true`, Types: map[string]interface{}{"mapStringBool": map[string]bool{"b": true}}, RunOutput: true, Output: map[string]interface{}{"a": map[string]bool{"b": true}}}, + {Script: `a = make(mapStringInt32); a["b"] = 1`, Types: map[string]interface{}{"mapStringInt32": map[string]int32{"b": int32(1)}}, RunOutput: int64(1), Output: map[string]interface{}{"a": map[string]int32{"b": int32(1)}}}, + {Script: `a = make(mapStringInt64); a["b"] = 1`, Types: map[string]interface{}{"mapStringInt64": map[string]int64{"b": int64(1)}}, RunOutput: int64(1), Output: map[string]interface{}{"a": map[string]int64{"b": int64(1)}}}, + {Script: `a = make(mapStringFloat32); a["b"] = 1.1`, Types: map[string]interface{}{"mapStringFloat32": map[string]float32{"b": float32(1.1)}}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": map[string]float32{"b": float32(1.1)}}}, + {Script: `a = make(mapStringFloat64); a["b"] = 1.1`, Types: map[string]interface{}{"mapStringFloat64": map[string]float64{"b": float64(1.1)}}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": map[string]float64{"b": float64(1.1)}}}, + {Script: `a = make(mapStringString); a["b"] = "b"`, Types: map[string]interface{}{"mapStringString": map[string]string{"b": "b"}}, RunOutput: "b", Output: map[string]interface{}{"a": map[string]string{"b": "b"}}}, + + {Script: `a = make(mapStringBool); a.b = true`, Types: map[string]interface{}{"mapStringBool": map[string]bool{"b": true}}, RunOutput: true, Output: map[string]interface{}{"a": map[string]bool{"b": true}}}, + + {Script: `a = make(mapInterfaceBool); a["b"] = true; a["b"]`, Types: map[string]interface{}{"mapInterfaceBool": map[interface{}]bool{}}, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]bool{"b": true}}}, + {Script: `a = make(mapInterfaceInt32); a["b"] = 1; a["b"]`, Types: map[string]interface{}{"mapInterfaceInt32": map[interface{}]int32{}}, RunOutput: int32(1), Output: map[string]interface{}{"a": map[interface{}]int32{"b": int32(1)}}}, + {Script: `a = make(mapInterfaceInt64); a["b"] = 1; a["b"]`, Types: map[string]interface{}{"mapInterfaceInt64": map[interface{}]int64{}}, RunOutput: int64(1), Output: map[string]interface{}{"a": map[interface{}]int64{"b": int64(1)}}}, + {Script: `a = make(mapInterfaceFloat32); a["b"] = 1.1; a["b"]`, Types: map[string]interface{}{"mapInterfaceFloat32": map[interface{}]float32{}}, RunOutput: float32(1.1), Output: map[string]interface{}{"a": map[interface{}]float32{"b": float32(1.1)}}}, + {Script: `a = make(mapInterfaceFloat64); a["b"] = 1.1; a["b"]`, Types: map[string]interface{}{"mapInterfaceFloat64": map[interface{}]float64{}}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": map[interface{}]float64{"b": float64(1.1)}}}, + {Script: `a = make(mapInterfaceString); a["b"] = "b"; a["b"]`, Types: map[string]interface{}{"mapInterfaceString": map[interface{}]string{}}, RunOutput: "b", Output: map[string]interface{}{"a": map[interface{}]string{"b": "b"}}}, + + {Script: `a = make(mapInterfaceBool); a.b = true; a.b`, Types: map[string]interface{}{"mapInterfaceBool": map[interface{}]bool{}}, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]bool{"b": true}}}, + {Script: `a = make(mapInterfaceInt32); a.b = 1; a.b`, Types: map[string]interface{}{"mapInterfaceInt32": map[interface{}]int32{}}, RunOutput: int32(1), Output: map[string]interface{}{"a": map[interface{}]int32{"b": int32(1)}}}, + {Script: `a = make(mapInterfaceInt64); a.b = 1; a.b`, Types: map[string]interface{}{"mapInterfaceInt64": map[interface{}]int64{}}, RunOutput: int64(1), Output: map[string]interface{}{"a": map[interface{}]int64{"b": int64(1)}}}, + {Script: `a = make(mapInterfaceFloat32); a.b = 1.1; a.b`, Types: map[string]interface{}{"mapInterfaceFloat32": map[interface{}]float32{}}, RunOutput: float32(1.1), Output: map[string]interface{}{"a": map[interface{}]float32{"b": float32(1.1)}}}, + {Script: `a = make(mapInterfaceFloat64); a.b = 1.1; a.b`, Types: map[string]interface{}{"mapInterfaceFloat64": map[interface{}]float64{}}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": map[interface{}]float64{"b": float64(1.1)}}}, + {Script: `a = make(mapInterfaceString); a.b = "b"; a.b`, Types: map[string]interface{}{"mapInterfaceString": map[interface{}]string{}}, RunOutput: "b", Output: map[string]interface{}{"a": map[interface{}]string{"b": "b"}}}, + + // map type errors + {Script: `map[int64]string {"a":"a"}`, RunError: fmt.Errorf("cannot use type string as type int64 as map key")}, + {Script: `map[string]int64 {"a":"a"}`, RunError: fmt.Errorf("cannot use type string as type int64 as map value")}, + {Script: `map[nilT]interface {"a":"a"}`, Types: map[string]interface{}{"nilT": nil}, RunError: fmt.Errorf("cannot make type nil")}, + {Script: `map[interface]nilT {"a":"a"}`, Types: map[string]interface{}{"nilT": nil}, RunError: fmt.Errorf("cannot make type nil")}, + {Script: `map[int64]int64 {1++:1}`, RunError: fmt.Errorf("invalid operation")}, + {Script: `map[int64]int64 {1:1++}`, RunError: fmt.Errorf("invalid operation")}, + + // map type + {Script: `map[string]interface {"a":nil}`, RunOutput: map[string]interface{}{"a": nil}}, + {Script: `map[string]bool {"a":true}`, RunOutput: map[string]bool{"a": true}}, + {Script: `map[string]int32 {"a":1}`, RunOutput: map[string]int32{"a": 1}}, + {Script: `map[string]int64 {"a":2}`, RunOutput: map[string]int64{"a": 2}}, + {Script: `map[string]float32 {"a":3.5}`, RunOutput: map[string]float32{"a": 3.5}}, + {Script: `map[string]float64 {"a":4.5}`, RunOutput: map[string]float64{"a": 4.5}}, + {Script: `map[string]string {"a":"a"}`, RunOutput: map[string]string{"a": "a"}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestSlicesAndMaps(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `a = [{"b": nil}]`, RunOutput: []interface{}{map[interface{}]interface{}{"b": interface{}(nil)}}, Output: map[string]interface{}{"a": []interface{}{map[interface{}]interface{}{"b": interface{}(nil)}}}}, + {Script: `a = [{"b": true}]`, RunOutput: []interface{}{map[interface{}]interface{}{"b": interface{}(true)}}, Output: map[string]interface{}{"a": []interface{}{map[interface{}]interface{}{"b": interface{}(true)}}}}, + {Script: `a = [{"b": 1}]`, RunOutput: []interface{}{map[interface{}]interface{}{"b": interface{}(int64(1))}}, Output: map[string]interface{}{"a": []interface{}{map[interface{}]interface{}{"b": interface{}(int64(1))}}}}, + {Script: `a = [{"b": 1.1}]`, RunOutput: []interface{}{map[interface{}]interface{}{"b": interface{}(float64(1.1))}}, Output: map[string]interface{}{"a": []interface{}{map[interface{}]interface{}{"b": interface{}(float64(1.1))}}}}, + {Script: `a = [{"b": "b"}]`, RunOutput: []interface{}{map[interface{}]interface{}{"b": interface{}("b")}}, Output: map[string]interface{}{"a": []interface{}{map[interface{}]interface{}{"b": interface{}("b")}}}}, + + {Script: `a = [{"b": nil}]; a[0]`, RunOutput: map[interface{}]interface{}{"b": interface{}(nil)}, Output: map[string]interface{}{"a": []interface{}{map[interface{}]interface{}{"b": interface{}(nil)}}}}, + {Script: `a = [{"b": true}]; a[0]`, RunOutput: map[interface{}]interface{}{"b": interface{}(true)}, Output: map[string]interface{}{"a": []interface{}{map[interface{}]interface{}{"b": interface{}(true)}}}}, + {Script: `a = [{"b": 1}]; a[0]`, RunOutput: map[interface{}]interface{}{"b": interface{}(int64(1))}, Output: map[string]interface{}{"a": []interface{}{map[interface{}]interface{}{"b": interface{}(int64(1))}}}}, + {Script: `a = [{"b": 1.1}]; a[0]`, RunOutput: map[interface{}]interface{}{"b": interface{}(float64(1.1))}, Output: map[string]interface{}{"a": []interface{}{map[interface{}]interface{}{"b": interface{}(float64(1.1))}}}}, + {Script: `a = [{"b": "b"}]; a[0]`, RunOutput: map[interface{}]interface{}{"b": interface{}("b")}, Output: map[string]interface{}{"a": []interface{}{map[interface{}]interface{}{"b": interface{}("b")}}}}, + + {Script: `a = {"b": []}`, RunOutput: map[interface{}]interface{}{"b": []interface{}{}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{}}}}, + {Script: `a = {"b": [nil]}`, RunOutput: map[interface{}]interface{}{"b": []interface{}{nil}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{nil}}}}, + {Script: `a = {"b": [true]}`, RunOutput: map[interface{}]interface{}{"b": []interface{}{true}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{true}}}}, + {Script: `a = {"b": [1]}`, RunOutput: map[interface{}]interface{}{"b": []interface{}{int64(1)}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{int64(1)}}}}, + {Script: `a = {"b": [1.1]}`, RunOutput: map[interface{}]interface{}{"b": []interface{}{float64(1.1)}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{float64(1.1)}}}}, + {Script: `a = {"b": ["b"]}`, RunOutput: map[interface{}]interface{}{"b": []interface{}{"b"}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{"b"}}}}, + + {Script: `a = {"b": []}; a.b`, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{}}}}, + {Script: `a = {"b": [nil]}; a.b`, RunOutput: []interface{}{nil}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{nil}}}}, + {Script: `a = {"b": [true]}; a.b`, RunOutput: []interface{}{true}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{true}}}}, + {Script: `a = {"b": [1]}; a.b`, RunOutput: []interface{}{int64(1)}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{int64(1)}}}}, + {Script: `a = {"b": [1.1]}; a.b`, RunOutput: []interface{}{float64(1.1)}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{float64(1.1)}}}}, + {Script: `a = {"b": ["b"]}; a.b`, RunOutput: []interface{}{"b"}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{"b"}}}}, + + {Script: `a.b = []`, Input: map[string]interface{}{"a": map[string][]interface{}{}}, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": map[string][]interface{}{"b": {}}}}, + {Script: `a.b = [nil]`, Input: map[string]interface{}{"a": map[string][]interface{}{}}, RunOutput: []interface{}{interface{}(nil)}, Output: map[string]interface{}{"a": map[string][]interface{}{"b": {interface{}(nil)}}}}, + {Script: `a.b = [true]`, Input: map[string]interface{}{"a": map[string][]interface{}{}}, RunOutput: []interface{}{true}, Output: map[string]interface{}{"a": map[string][]interface{}{"b": {true}}}}, + {Script: `a.b = [1]`, Input: map[string]interface{}{"a": map[string][]interface{}{}}, RunOutput: []interface{}{int64(1)}, Output: map[string]interface{}{"a": map[string][]interface{}{"b": {int64(1)}}}}, + {Script: `a.b = [1.1]`, Input: map[string]interface{}{"a": map[string][]interface{}{}}, RunOutput: []interface{}{float64(1.1)}, Output: map[string]interface{}{"a": map[string][]interface{}{"b": {float64(1.1)}}}}, + {Script: `a.b = ["b"]`, Input: map[string]interface{}{"a": map[string][]interface{}{}}, RunOutput: []interface{}{"b"}, Output: map[string]interface{}{"a": map[string][]interface{}{"b": {"b"}}}}, + + {Script: `b[0] = [nil]; a.b = b`, Input: map[string]interface{}{"a": map[string][][]interface{}{}, "b": [][]interface{}{}}, RunOutput: [][]interface{}{{interface{}(nil)}}, Output: map[string]interface{}{"a": map[string][][]interface{}{"b": {{interface{}(nil)}}}, "b": [][]interface{}{{interface{}(nil)}}}}, + {Script: `b[0] = [true]; a.b = b`, Input: map[string]interface{}{"a": map[string][][]interface{}{}, "b": [][]interface{}{}}, RunOutput: [][]interface{}{{true}}, Output: map[string]interface{}{"a": map[string][][]interface{}{"b": {{true}}}, "b": [][]interface{}{{true}}}}, + {Script: `b[0] = [1]; a.b = b`, Input: map[string]interface{}{"a": map[string][][]interface{}{}, "b": [][]interface{}{}}, RunOutput: [][]interface{}{{int64(1)}}, Output: map[string]interface{}{"a": map[string][][]interface{}{"b": {{int64(1)}}}, "b": [][]interface{}{{int64(1)}}}}, + {Script: `b[0] = [1.1]; a.b = b`, Input: map[string]interface{}{"a": map[string][][]interface{}{}, "b": [][]interface{}{}}, RunOutput: [][]interface{}{{float64(1.1)}}, Output: map[string]interface{}{"a": map[string][][]interface{}{"b": {{float64(1.1)}}}, "b": [][]interface{}{{float64(1.1)}}}}, + {Script: `b[0] = ["b"]; a.b = b`, Input: map[string]interface{}{"a": map[string][][]interface{}{}, "b": [][]interface{}{}}, RunOutput: [][]interface{}{{"b"}}, Output: map[string]interface{}{"a": map[string][][]interface{}{"b": {{"b"}}}, "b": [][]interface{}{{"b"}}}}, + + {Script: `a`, Input: map[string]interface{}{"a": map[string][][]interface{}{}}, RunOutput: map[string][][]interface{}{}, Output: map[string]interface{}{"a": map[string][][]interface{}{}}}, + {Script: `a.b = 1`, Input: map[string]interface{}{"a": map[string][][]interface{}{}}, RunError: fmt.Errorf("type int64 cannot be assigned to type [][]interface {} for map"), RunOutput: nil, Output: map[string]interface{}{"a": map[string][][]interface{}{}}}, + {Script: `a["b"] = 1`, Input: map[string]interface{}{"a": map[string][][]interface{}{}}, RunError: fmt.Errorf("type int64 cannot be assigned to type [][]interface {} for map"), RunOutput: nil, Output: map[string]interface{}{"a": map[string][][]interface{}{}}}, + + {Script: `a = {}; a.b = []; a.b += 1; a.b[0] = {}; a.b[0].c = []; a.b[0].c += 2; a.b[0].c[0]`, RunOutput: int64(2), Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": []interface{}{map[interface{}]interface{}{"c": []interface{}{int64(2)}}}}}}, + + {Script: `a = {}; a.b = b`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: [][]interface{}{}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: [][]interface{}{}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b[0]`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunError: fmt.Errorf("index out of range"), RunOutput: nil, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{}}, "b": [][]interface{}{}}}, + + {Script: `a = {}; a.b = b; a.b[0] = []`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: []interface{}{}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{}}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b[0] = [nil]`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: []interface{}{nil}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{nil}}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b[0] = [nil]; a.b`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: [][]interface{}{{nil}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{nil}}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b[0] = [true]; a.b`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: [][]interface{}{{true}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{true}}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b[0] = [1]; a.b`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: [][]interface{}{{int64(1)}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{int64(1)}}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b[0] = [1.1]; a.b`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: [][]interface{}{{float64(1.1)}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{float64(1.1)}}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b[0] = ["c"]; a.b`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: [][]interface{}{{"c"}}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{"c"}}}, "b": [][]interface{}{}}}, + + {Script: `a = {}; a.b = b; a.b[0] = [nil]; a.b[0]`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: []interface{}{nil}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{nil}}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b[0] = [true]; a.b[0]`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: []interface{}{true}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{true}}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b[0] = [1]; a.b[0]`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: []interface{}{int64(1)}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{int64(1)}}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b[0] = [1.1]; a.b[0]`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: []interface{}{float64(1.1)}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{float64(1.1)}}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b[0] = ["c"]; a.b[0]`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: []interface{}{"c"}, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{"c"}}}, "b": [][]interface{}{}}}, + + {Script: `a = {}; a.b = b; a.b[0] = [nil]; a.b[0][0]`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: nil, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{nil}}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b[0] = [true]; a.b[0][0]`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{true}}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b[0] = [1]; a.b[0][0]`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: int64(1), Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{int64(1)}}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b[0] = [1.1]; a.b[0][0]`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{float64(1.1)}}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b[0] = ["c"]; a.b[0][0]`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: "c", Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{"c"}}}, "b": [][]interface{}{}}}, + + {Script: `a = {}; a.b = b; a.b[0] = [nil]; a.b[0][1] = nil`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: nil, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{nil, nil}}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b[0] = [true]; a.b[0][1] = true`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{true, true}}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b[0] = [1]; a.b[0][1] = 2`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: int64(2), Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{int64(1), int64(2)}}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b[0] = [1.1]; a.b[0][1] = 2.2`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: float64(2.2), Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{float64(1.1), float64(2.2)}}}, "b": [][]interface{}{}}}, + {Script: `a = {}; a.b = b; a.b[0] = ["c"]; a.b[0][1] = "d"`, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: "d", Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": [][]interface{}{{"c", "d"}}}, "b": [][]interface{}{}}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestMakeSlicesAndMaps(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `make([]aMap)`, Types: map[string]interface{}{"aMap": map[string]interface{}{}}, RunOutput: []map[string]interface{}{}}, + {Script: `make([][]aMap)`, Types: map[string]interface{}{"aMap": map[string]interface{}{}}, RunOutput: [][]map[string]interface{}{}}, + + {Script: `make(mapSlice2x)`, Types: map[string]interface{}{"mapSlice2x": map[string][][]interface{}{}}, RunOutput: map[string][][]interface{}{}}, + {Script: `a = make(mapSlice2x)`, Types: map[string]interface{}{"mapSlice2x": map[string][][]interface{}{}}, RunOutput: map[string][][]interface{}{}, Output: map[string]interface{}{"a": map[string][][]interface{}{}}}, + {Script: `a = make(mapSlice2x); a`, Types: map[string]interface{}{"mapSlice2x": map[string][][]interface{}{}}, RunOutput: map[string][][]interface{}{}, Output: map[string]interface{}{"a": map[string][][]interface{}{}}}, + {Script: `a = make(mapSlice2x); a.b = b`, Types: map[string]interface{}{"mapSlice2x": map[string][][]interface{}{}}, Input: map[string]interface{}{"b": [][]interface{}{}}, RunOutput: [][]interface{}{}, Output: map[string]interface{}{"a": map[string][][]interface{}{"b": {}}, "b": [][]interface{}{}}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestMakeSlicesData(t *testing.T) { + t.Parallel() + + stmts, err := parser.ParseSrc("make(slice)") + if err != nil { + t.Errorf("ParseSrc error - received %v - expected: %v", err, nil) + } + + e := env.NewEnv() + err = e.DefineType("slice", []string{}) + if err != nil { + t.Errorf("DefineType error - received %v - expected: %v", err, nil) + } + + value, err := Run(e, nil, stmts) + if err != nil { + t.Errorf("Run error - received %v - expected: %v", err, nil) + } + if !reflect.DeepEqual(value, []string{}) { + t.Errorf("Run value - received %#v - expected: %#v", value, []string{}) + } + + a := value.([]string) + if len(a) != 0 { + t.Errorf("len value - received %#v - expected: %#v", len(a), 0) + } + a = append(a, "a") + if a[0] != "a" { + t.Errorf("Get value - received %#v - expected: %#v", a[0], "a") + } + if len(a) != 1 { + t.Errorf("len value - received %#v - expected: %#v", len(a), 1) + } + + stmts, err = parser.ParseSrc("make([]string)") + if err != nil { + t.Errorf("ParseSrc error - received %v - expected: %v", err, nil) + } + + e = env.NewEnv() + err = e.DefineType("string", "a") + if err != nil { + t.Errorf("DefineType error - received %v - expected: %v", err, nil) + } + + value, err = Run(e, nil, stmts) + if err != nil { + t.Errorf("Run error - received %v - expected: %v", err, nil) + } + if !reflect.DeepEqual(value, []string{}) { + t.Errorf("Run value - received %#v - expected: %#v", value, []string{}) + } + + b := value.([]string) + if len(b) != 0 { + t.Errorf("len value - received %#v - expected: %#v", len(b), 0) + } + b = append(b, "b") + if b[0] != "b" { + t.Errorf("Get value - received %#v - expected: %#v", b[0], "b") + } + if len(b) != 1 { + t.Errorf("len value - received %#v - expected: %#v", len(b), 1) + } +} + +func TestMakeMapsData(t *testing.T) { + t.Parallel() + + stmts, err := parser.ParseSrc("make(aMap)") + if err != nil { + t.Errorf("ParseSrc error - received %v - expected: %v", err, nil) + } + + // test normal map + e := env.NewEnv() + err = e.DefineType("aMap", map[string]string{}) + if err != nil { + t.Errorf("DefineType error - received %v - expected: %v", err, nil) + } + + value, err := Run(e, nil, stmts) + if err != nil { + t.Errorf("Run error - received %v - expected: %v", err, nil) + } + if !reflect.DeepEqual(value, map[string]string{}) { + t.Errorf("Run value - received %#v - expected: %#v", value, map[string]string{}) + } + + a := value.(map[string]string) + a["a"] = "a" + if a["a"] != "a" { + t.Errorf("Get value - received %#v - expected: %#v", a["a"], "a") + } + + // test url Values map + e = env.NewEnv() + err = e.DefineType("aMap", url.Values{}) + if err != nil { + t.Errorf("DefineType error - received %v - expected: %v", err, nil) + } + + value, err = Run(e, nil, stmts) + if err != nil { + t.Errorf("Run error - received %v - expected: %v", err, nil) + } + if !reflect.DeepEqual(value, url.Values{}) { + t.Errorf("Run value - received %#v - expected: %#v", value, url.Values{}) + } + + b := value.(url.Values) + b.Set("b", "b") + if b.Get("b") != "b" { + t.Errorf("Get value - received %#v - expected: %#v", b.Get("b"), "b") + } +} + +func TestStructs(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `a["B"]`, Input: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{}}, + RunError: fmt.Errorf("type struct does not support index operation"), + Output: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{}}}, + {Script: `a.C`, Input: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{}}, + RunError: fmt.Errorf("no member named 'C' for struct"), + Output: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{}}}, + + {Script: `a.B`, Input: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{}}, + RunOutput: nil, + Output: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{}}}, + {Script: `a.B`, Input: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{A: nil, B: nil}}, + RunOutput: nil, + Output: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{A: nil, B: nil}}}, + {Script: `a.B`, Input: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{A: int32(1), B: int32(2)}}, + RunOutput: int32(2), + Output: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{A: int32(1), B: int32(2)}}}, + {Script: `a.B`, Input: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{A: int64(1), B: int64(2)}}, + RunOutput: int64(2), + Output: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{A: int64(1), B: int64(2)}}}, + {Script: `a.B`, Input: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{A: float32(1.1), B: float32(2.2)}}, + RunOutput: float32(2.2), + Output: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{A: float32(1.1), B: float32(2.2)}}}, + {Script: `a.B`, Input: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{A: float64(1.1), B: float64(2.2)}}, + RunOutput: float64(2.2), + Output: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{A: float64(1.1), B: float64(2.2)}}}, + {Script: `a.B`, Input: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{A: "a", B: "b"}}, + RunOutput: "b", + Output: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{A: "a", B: "b"}}}, + + {Script: `a.B`, Input: map[string]interface{}{"a": struct { + A bool + B bool + }{}}, + RunOutput: false, + Output: map[string]interface{}{"a": struct { + A bool + B bool + }{}}}, + {Script: `a.B`, Input: map[string]interface{}{"a": struct { + A int32 + B int32 + }{}}, + RunOutput: int32(0), + Output: map[string]interface{}{"a": struct { + A int32 + B int32 + }{}}}, + {Script: `a.B`, Input: map[string]interface{}{"a": struct { + A int64 + B int64 + }{}}, + RunOutput: int64(0), + Output: map[string]interface{}{"a": struct { + A int64 + B int64 + }{}}}, + {Script: `a.B`, Input: map[string]interface{}{"a": struct { + A float32 + B float32 + }{}}, + RunOutput: float32(0), + Output: map[string]interface{}{"a": struct { + A float32 + B float32 + }{}}}, + {Script: `a.B`, Input: map[string]interface{}{"a": struct { + A float64 + B float64 + }{}}, + RunOutput: float64(0), + Output: map[string]interface{}{"a": struct { + A float64 + B float64 + }{}}}, + {Script: `a.B`, Input: map[string]interface{}{"a": struct { + A string + B string + }{}}, + RunOutput: "", + Output: map[string]interface{}{"a": struct { + A string + B string + }{}}}, + + {Script: `a.B`, Input: map[string]interface{}{"a": struct { + A bool + B bool + }{A: true, B: true}}, + RunOutput: true, + Output: map[string]interface{}{"a": struct { + A bool + B bool + }{A: true, B: true}}}, + {Script: `a.B`, Input: map[string]interface{}{"a": struct { + A int32 + B int32 + }{A: int32(1), B: int32(2)}}, + RunOutput: int32(2), + Output: map[string]interface{}{"a": struct { + A int32 + B int32 + }{A: int32(1), B: int32(2)}}}, + {Script: `a.B`, Input: map[string]interface{}{"a": struct { + A int64 + B int64 + }{A: int64(1), B: int64(2)}}, + RunOutput: int64(2), + Output: map[string]interface{}{"a": struct { + A int64 + B int64 + }{A: int64(1), B: int64(2)}}}, + {Script: `a.B`, Input: map[string]interface{}{"a": struct { + A float32 + B float32 + }{A: float32(1.1), B: float32(2.2)}}, + RunOutput: float32(2.2), + Output: map[string]interface{}{"a": struct { + A float32 + B float32 + }{A: float32(1.1), B: float32(2.2)}}}, + {Script: `a.B`, Input: map[string]interface{}{"a": struct { + A float64 + B float64 + }{A: float64(1.1), B: float64(2.2)}}, + RunOutput: float64(2.2), + Output: map[string]interface{}{"a": struct { + A float64 + B float64 + }{A: float64(1.1), B: float64(2.2)}}}, + {Script: `a.B`, Input: map[string]interface{}{"a": struct { + A string + B string + }{A: "a", B: "b"}}, + RunOutput: "b", + Output: map[string]interface{}{"a": struct { + A string + B string + }{A: "a", B: "b"}}}, + + {Script: `a.C = 3`, Input: map[string]interface{}{ + "a": struct { + A interface{} + B interface{} + }{A: int64(1), B: int64(2)}, + }, + RunError: fmt.Errorf("no member named 'C' for struct"), + Output: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{A: int64(1), B: int64(2)}}}, + + {Script: `a.B = 3`, Input: map[string]interface{}{"a": &struct { + A interface{} + B interface{} + }{A: int64(1), B: int64(2)}}, + RunOutput: int64(3), + Output: map[string]interface{}{"a": &struct { + A interface{} + B interface{} + }{A: int64(1), B: int64(3)}}}, + + {Script: `a.B = 3; a = *a`, Input: map[string]interface{}{"a": &struct { + A interface{} + B interface{} + }{A: int64(1), B: int64(2)}}, + RunOutput: struct { + A interface{} + B interface{} + }{A: int64(1), B: int64(3)}, + Output: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{A: int64(1), B: int64(3)}}}, + + // nil tests + {Script: `a.A = nil; a.B = nil; a.C = nil;`, Input: map[string]interface{}{"a": &struct { + A *bool + B *int32 + C *int64 + }{A: new(bool), B: new(int32), C: new(int64)}}, + RunOutput: nil, + Output: map[string]interface{}{"a": &struct { + A *bool + B *int32 + C *int64 + }{A: nil, B: nil, C: nil}}}, + {Script: `a.A = nil; a.B = nil; a.C = nil;`, Input: map[string]interface{}{"a": &struct { + A *float32 + B *float64 + C *string + }{A: new(float32), B: new(float64), C: new(string)}}, + RunOutput: nil, + Output: map[string]interface{}{"a": &struct { + A *float32 + B *float64 + C *string + }{A: nil, B: nil, C: nil}}}, + {Script: `a.A = nil; a.B = nil; a.C = nil;`, Input: map[string]interface{}{"a": &struct { + A interface{} + B []interface{} + C [][]interface{} + }{A: "a", B: []interface{}{"b"}, C: [][]interface{}{{"c"}}}}, + RunOutput: nil, + Output: map[string]interface{}{"a": &struct { + A interface{} + B []interface{} + C [][]interface{} + }{A: nil, B: nil, C: nil}}}, + {Script: `a.A = nil; a.B = nil; a.C = nil; a.D = nil;`, Input: map[string]interface{}{"a": &struct { + A map[string]string + B map[string]interface{} + C map[interface{}]string + D map[interface{}]interface{} + }{A: map[string]string{"a": "a"}, B: map[string]interface{}{"b": "b"}, C: map[interface{}]string{"c": "c"}, D: map[interface{}]interface{}{"d": "d"}}}, + RunOutput: nil, + Output: map[string]interface{}{"a": &struct { + A map[string]string + B map[string]interface{} + C map[interface{}]string + D map[interface{}]interface{} + }{A: nil, B: nil, C: nil, D: nil}}}, + {Script: `a.A.AA = nil;`, Input: map[string]interface{}{"a": &struct { + A struct{ AA *int64 } + }{A: struct{ AA *int64 }{AA: new(int64)}}}, + RunOutput: nil, + Output: map[string]interface{}{"a": &struct { + A struct{ AA *int64 } + }{A: struct{ AA *int64 }{AA: nil}}}}, + {Script: `a.A = nil;`, Input: map[string]interface{}{"a": &struct { + A *struct{ AA *int64 } + }{A: &struct{ AA *int64 }{AA: new(int64)}}}, + RunOutput: nil, + Output: map[string]interface{}{"a": &struct { + A *struct{ AA *int64 } + }{A: nil}}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestMakeStructs(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `a = make(struct1)`, Types: map[string]interface{}{"struct1": &testStruct1{}}, RunOutput: &testStruct1{}, Output: map[string]interface{}{"a": &testStruct1{}}}, + {Script: `a = make(struct2)`, Types: map[string]interface{}{"struct2": &testStruct2{}}, RunOutput: &testStruct2{}, Output: map[string]interface{}{"a": &testStruct2{}}}, + {Script: `make(struct1)`, Types: map[string]interface{}{"struct1": &struct { + A interface{} + B interface{} + }{}}, + RunOutput: &struct { + A interface{} + B interface{} + }{}}, + + {Script: `a = make(struct1)`, Types: map[string]interface{}{"struct1": &struct { + A interface{} + B interface{} + }{}}, + RunOutput: &struct { + A interface{} + B interface{} + }{}, + Output: map[string]interface{}{"a": &struct { + A interface{} + B interface{} + }{}}}, + + {Script: `a = make(struct1); a.A = 3; a.B = 4`, Types: map[string]interface{}{"struct1": &struct { + A interface{} + B interface{} + }{}}, + RunOutput: int64(4), + Output: map[string]interface{}{"a": &struct { + A interface{} + B interface{} + }{A: interface{}(int64(3)), B: interface{}(int64(4))}}}, + + {Script: `a = make(struct1); a = *a; a.A = 3; a.B = 4`, Types: map[string]interface{}{"struct1": &struct { + A interface{} + B interface{} + }{}}, + RunOutput: int64(4), + Output: map[string]interface{}{"a": struct { + A interface{} + B interface{} + }{A: interface{}(int64(3)), B: interface{}(int64(4))}}}, + + {Script: `a = make(struct1); a.A = func () { return 1 }; a.A()`, Types: map[string]interface{}{"struct1": &struct { + A interface{} + B interface{} + }{}}, + RunOutput: int64(1)}, + {Script: `a = make(struct1); a.A = func () { return 1 }; a = *a; a.A()`, Types: map[string]interface{}{"struct1": &struct { + A interface{} + B interface{} + }{}}, + RunOutput: int64(1)}, + + // make struct - new lines + {Script: `make(struct { A int64, B float64 })`, RunOutput: struct { + A int64 + B float64 + }{}}, + {Script: `make(struct { +A int64, B float64 })`, RunOutput: struct { + A int64 + B float64 + }{}}, + {Script: `make(struct { A int64, +B float64 })`, RunOutput: struct { + A int64 + B float64 + }{}}, + {Script: `make(struct { A int64, B float64 +})`, RunOutput: struct { + A int64 + B float64 + }{}}, + {Script: ` +make(struct { + A int64, + B float64 +})`, RunOutput: struct { + A int64 + B float64 + }{}}, + + // make struct - with basic types + {Script: ` +make(struct { + A bool, + B int32, + C int64, + D float32, + E float64, + F string +})`, RunOutput: struct { + A bool + B int32 + C int64 + D float32 + E float64 + F string + }{}}, + + // make struct - with other types + {Script: ` +make(struct { + A *int64, + B []int64, + C map[string]int64 +})`, RunOutput: struct { + A *int64 + B []int64 + C map[string]int64 + }{A: (*int64)(nil), B: []int64{}, C: map[string]int64{}}}, + + // make struct within structs + {Script: ` +make(struct { + A struct { + AA int64, + AB float64 + }, + B struct { + BA []int64, + BB map[string]int64 + } +})`, RunOutput: struct { + A struct { + AA int64 + AB float64 + } + B struct { + BA []int64 + BB map[string]int64 + } + }{A: struct { + AA int64 + AB float64 + }{AA: 0, AB: 0}, B: struct { + BA []int64 + BB map[string]int64 + }{BA: []int64{}, BB: map[string]int64{}}}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} diff --git a/src/tool/run/vm/vmConvertToX.go b/src/tool/run/vm/vmConvertToX.go new file mode 100644 index 0000000..38a0032 --- /dev/null +++ b/src/tool/run/vm/vmConvertToX.go @@ -0,0 +1,206 @@ +package vm + +import ( + "context" + "fmt" + "reflect" +) + +// reflectValueSlicetoInterfaceSlice convert from a slice of reflect.Value to a interface slice +// returned in normal reflect.Value form +func reflectValueSlicetoInterfaceSlice(valueSlice []reflect.Value) reflect.Value { + interfaceSlice := make([]interface{}, 0, len(valueSlice)) + for _, value := range valueSlice { + if value.Kind() == reflect.Interface && !value.IsNil() { + value = value.Elem() + } + if value.CanInterface() { + interfaceSlice = append(interfaceSlice, value.Interface()) + } else { + interfaceSlice = append(interfaceSlice, nil) + } + } + return reflect.ValueOf(interfaceSlice) +} + +// convertReflectValueToType trys to covert the reflect.Value to the reflect.Type +// if it can not, it returns the original rv and an error +func convertReflectValueToType(rv reflect.Value, rt reflect.Type) (reflect.Value, error) { + if rt == interfaceType || rv.Type() == rt { + // if reflect.Type is interface or the types match, return the provided reflect.Value + return rv, nil + } + if rv.Type().ConvertibleTo(rt) { + // if reflect can covert, do that conversion and return + return rv.Convert(rt), nil + } + if (rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array) && + (rt.Kind() == reflect.Slice || rt.Kind() == reflect.Array) { + // covert slice or array + return convertSliceOrArray(rv, rt) + } + if rv.Kind() == rt.Kind() { + // kind matches + switch rv.Kind() { + case reflect.Map: + // convert map + return convertMap(rv, rt) + case reflect.Func: + // for runVMFunction conversions, call convertVMFunctionToType + return convertVMFunctionToType(rv, rt) + case reflect.Ptr: + // both rv and rt are pointers, convert what they are pointing to + value, err := convertReflectValueToType(rv.Elem(), rt.Elem()) + if err != nil { + return rv, err + } + // need to make a new value to be able to set it + ptrV, err := makeValue(rt) + if err != nil { + return rv, err + } + // set value and return new pointer + ptrV.Elem().Set(value) + return ptrV, nil + } + } + if rv.Type() == interfaceType { + if rv.IsNil() { + // return nil of correct type + return reflect.Zero(rt), nil + } + // try to convert the element + return convertReflectValueToType(rv.Elem(), rt) + } + + if rv.Type() == stringType { + if rt == byteType { + aString := rv.String() + if len(aString) < 1 { + return reflect.Zero(rt), nil + } + if len(aString) > 1 { + return rv, errInvalidTypeConversion + } + return reflect.ValueOf(aString[0]), nil + } + if rt == runeType { + aString := rv.String() + if len(aString) < 1 { + return reflect.Zero(rt), nil + } + if len(aString) > 1 { + return rv, errInvalidTypeConversion + } + return reflect.ValueOf(rune(aString[0])), nil + } + } + + // TODO: need to handle the case where either rv or rt are a pointer but not both + + return rv, errInvalidTypeConversion +} + +// convertSliceOrArray trys to covert the reflect.Value slice or array to the slice or array reflect.Type +func convertSliceOrArray(rv reflect.Value, rt reflect.Type) (reflect.Value, error) { + rtElemType := rt.Elem() + + // try to covert elements to new slice/array + var value reflect.Value + if rt.Kind() == reflect.Slice { + // make slice + value = reflect.MakeSlice(rt, rv.Len(), rv.Len()) + } else { + // make array + value = reflect.New(rt).Elem() + } + + var err error + var v reflect.Value + for i := 0; i < rv.Len(); i++ { + v, err = convertReflectValueToType(rv.Index(i), rtElemType) + if err != nil { + return rv, err + } + value.Index(i).Set(v) + } + + // return new converted slice or array + return value, nil +} + +// convertVMFunctionToType is for translating a runVMFunction into the correct type +// so it can be passed to a Go function argument with the correct static types +// it creates a translate function runVMConvertFunction +func convertVMFunctionToType(rv reflect.Value, rt reflect.Type) (reflect.Value, error) { + // only translates runVMFunction type + if !checkIfRunVMFunction(rv.Type()) { + return rv, errInvalidTypeConversion + } + + // create runVMConvertFunction to match reflect.Type + // this function is being called by the Go function + runVMConvertFunction := func(in []reflect.Value) []reflect.Value { + // note: this function is being called by another reflect Call + // only way to pass along any errors is by panic + + // make the reflect.Value slice of each of the VM reflect.Value + args := make([]reflect.Value, 0, rt.NumIn()+1) + // for runVMFunction first arg is always context + // TOFIX: use normal context + args = append(args, reflect.ValueOf(context.Background())) + for i := 0; i < rt.NumIn(); i++ { + // have to do the double reflect.ValueOf that runVMFunction expects + args = append(args, reflect.ValueOf(in[i])) + } + + // Call runVMFunction + rvs := rv.Call(args) + + // call processCallReturnValues to process runVMFunction return values + // returns normal VM reflect.Value form + rv, err := processCallReturnValues(rvs, true, false) + if err != nil { + panic(err) + } + + if rt.NumOut() < 1 { + // Go function does not want any return values, so give it none + return []reflect.Value{} + } + if rt.NumOut() < 2 { + // Go function wants one return value + // will try to covert to reflect.Value correct type and return + rv, err = convertReflectValueToType(rv, rt.Out(0)) + if err != nil { + panic("function wants return type " + rt.Out(0).String() + " but received type " + rv.Type().String()) + } + return []reflect.Value{rv} + } + + // Go function wants more than one return value + // make sure we have a slice/array with enought values + + if rv.Kind() != reflect.Slice && rv.Kind() != reflect.Array { + panic(fmt.Sprintf("function wants %v return values but received %v", rt.NumOut(), rv.Kind().String())) + } + if rv.Len() < rt.NumOut() { + panic(fmt.Sprintf("function wants %v return values but received %v values", rt.NumOut(), rv.Len())) + } + + // try to covert each value in slice to wanted type and put into a reflect.Value slice + rvs = make([]reflect.Value, rt.NumOut()) + for i := 0; i < rv.Len(); i++ { + rvs[i], err = convertReflectValueToType(rv.Index(i), rt.Out(i)) + if err != nil { + panic("function wants return type " + rt.Out(i).String() + " but received type " + rvs[i].Type().String()) + } + } + + // return created reflect.Value slice + return rvs + } + + // make the reflect.Value function that calls runVMConvertFunction + return reflect.MakeFunc(rt, runVMConvertFunction), nil +} diff --git a/src/tool/run/vm/vmConvertToXGo112.go b/src/tool/run/vm/vmConvertToXGo112.go new file mode 100644 index 0000000..b42d3f5 --- /dev/null +++ b/src/tool/run/vm/vmConvertToXGo112.go @@ -0,0 +1,37 @@ +// +build go1.12 + +package vm + +import ( + "reflect" +) + +// convertMap trys to covert the reflect.Value map to the map reflect.Type +func convertMap(rv reflect.Value, rt reflect.Type) (reflect.Value, error) { + rtKey := rt.Key() + rtElem := rt.Elem() + + // create new map + // note creating slice as work around to create map + // just doing MakeMap can give incorrect type for defined types + newMap := reflect.MakeSlice(reflect.SliceOf(rt), 0, 1) + newMap = reflect.Append(newMap, reflect.MakeMap(reflect.MapOf(rtKey, rtElem))).Index(0) + + // copy keys to new map + // For Go 1.12 and after can use MapRange + mapIter := rv.MapRange() + var value reflect.Value + for mapIter.Next() { + newKey, err := convertReflectValueToType(mapIter.Key(), rtKey) + if err != nil { + return rv, err + } + value, err = convertReflectValueToType(mapIter.Value(), rtElem) + if err != nil { + return rv, err + } + newMap.SetMapIndex(newKey, value) + } + + return newMap, nil +} diff --git a/src/tool/run/vm/vmConvertToXNotGo112.go b/src/tool/run/vm/vmConvertToXNotGo112.go new file mode 100644 index 0000000..afa2248 --- /dev/null +++ b/src/tool/run/vm/vmConvertToXNotGo112.go @@ -0,0 +1,38 @@ +// +build !go1.12 + +package vm + +import ( + "reflect" +) + +// convertMap trys to covert the reflect.Value map to the map reflect.Type +func convertMap(rv reflect.Value, rt reflect.Type) (reflect.Value, error) { + rtKey := rt.Key() + rtElem := rt.Elem() + + // create new map + // note creating slice as work around to create map + // just doing MakeMap can give incorrect type for defined types + newMap := reflect.MakeSlice(reflect.SliceOf(rt), 0, 1) + newMap = reflect.Append(newMap, reflect.MakeMap(reflect.MapOf(rtKey, rtElem))).Index(0) + + // copy keys to new map + // Before Go 1.12 the only way to do this is to get all the keys. + // Note this is costly for large maps. + mapKeys := rv.MapKeys() + for i := 0; i < len(mapKeys); i++ { + newKey, err := convertReflectValueToType(mapKeys[i], rtKey) + if err != nil { + return rv, err + } + value := rv.MapIndex(mapKeys[i]) + value, err = convertReflectValueToType(value, rtElem) + if err != nil { + return rv, err + } + newMap.SetMapIndex(newKey, value) + } + + return newMap, nil +} diff --git a/src/tool/run/vm/vmExpr.go b/src/tool/run/vm/vmExpr.go new file mode 100644 index 0000000..4644626 --- /dev/null +++ b/src/tool/run/vm/vmExpr.go @@ -0,0 +1,761 @@ +package vm + +import ( + "reflect" + + "github.com/surdeus/goblin/src/tool/run/ast" + "github.com/surdeus/goblin/src/tool/run/env" +) + +// invokeExpr evaluates one expression. +func (runInfo *runInfoStruct) invokeExpr() { + switch expr := runInfo.expr.(type) { + + // OpExpr + case *ast.OpExpr: + runInfo.operator = expr.Op + runInfo.invokeOperator() + + // IdentExpr + case *ast.IdentExpr: + runInfo.rv, runInfo.err = runInfo.env.GetValue(expr.Lit) + if runInfo.err != nil { + runInfo.err = newError(expr, runInfo.err) + } + + // LiteralExpr + case *ast.LiteralExpr: + runInfo.rv = expr.Literal + + // ArrayExpr + case *ast.ArrayExpr: + if expr.TypeData == nil { + slice := make([]interface{}, len(expr.Exprs)) + var i int + for i, runInfo.expr = range expr.Exprs { + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + slice[i] = runInfo.rv.Interface() + } + runInfo.rv = reflect.ValueOf(slice) + return + } + + t := makeType(runInfo, expr.TypeData) + if runInfo.err != nil { + runInfo.rv = nilValue + return + } + if t == nil { + runInfo.err = newStringError(expr, "cannot make type nil") + runInfo.rv = nilValue + return + } + + slice := reflect.MakeSlice(t, len(expr.Exprs), len(expr.Exprs)) + var i int + valueType := t.Elem() + for i, runInfo.expr = range expr.Exprs { + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + + runInfo.rv, runInfo.err = convertReflectValueToType(runInfo.rv, valueType) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "cannot use type "+runInfo.rv.Type().String()+" as type "+valueType.String()+" as slice value") + runInfo.rv = nilValue + return + } + + slice.Index(i).Set(runInfo.rv) + } + runInfo.rv = slice + + // MapExpr + case *ast.MapExpr: + if expr.TypeData == nil { + var i int + var key reflect.Value + m := make(map[interface{}]interface{}, len(expr.Keys)) + for i, runInfo.expr = range expr.Keys { + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + key = runInfo.rv + + runInfo.expr = expr.Values[i] + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + + m[key.Interface()] = runInfo.rv.Interface() + } + runInfo.rv = reflect.ValueOf(m) + return + } + + t := makeType(runInfo, expr.TypeData) + if runInfo.err != nil { + runInfo.rv = nilValue + return + } + if t == nil { + runInfo.err = newStringError(expr, "cannot make type nil") + runInfo.rv = nilValue + return + } + + runInfo.rv, runInfo.err = makeValue(t) + if runInfo.err != nil { + runInfo.rv = nilValue + return + } + + var i int + var key reflect.Value + m := runInfo.rv + keyType := t.Key() + valueType := t.Elem() + for i, runInfo.expr = range expr.Keys { + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + key, runInfo.err = convertReflectValueToType(runInfo.rv, keyType) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "cannot use type "+key.Type().String()+" as type "+keyType.String()+" as map key") + runInfo.rv = nilValue + return + } + + runInfo.expr = expr.Values[i] + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + runInfo.rv, runInfo.err = convertReflectValueToType(runInfo.rv, valueType) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "cannot use type "+runInfo.rv.Type().String()+" as type "+valueType.String()+" as map value") + runInfo.rv = nilValue + return + } + + m.SetMapIndex(key, runInfo.rv) + } + runInfo.rv = m + + // DerefExpr + case *ast.DerefExpr: + runInfo.expr = expr.Expr + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + + if runInfo.rv.Kind() != reflect.Ptr { + runInfo.err = newStringError(expr.Expr, "cannot deference non-pointer") + runInfo.rv = nilValue + return + } + runInfo.rv = runInfo.rv.Elem() + + // AddrExpr + case *ast.AddrExpr: + runInfo.expr = expr.Expr + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + + if runInfo.rv.CanAddr() { + runInfo.rv = runInfo.rv.Addr() + } else { + i := runInfo.rv.Interface() + runInfo.rv = reflect.ValueOf(&i) + } + + // UnaryExpr + case *ast.UnaryExpr: + runInfo.expr = expr.Expr + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + + switch expr.Operator { + case "-": + switch runInfo.rv.Kind() { + case reflect.Int64: + runInfo.rv = reflect.ValueOf(-runInfo.rv.Int()) + case reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int, reflect.Bool: + runInfo.rv = reflect.ValueOf(-toInt64(runInfo.rv)) + case reflect.Float64: + runInfo.rv = reflect.ValueOf(-runInfo.rv.Float()) + default: + runInfo.rv = reflect.ValueOf(-toFloat64(runInfo.rv)) + } + case "^": + runInfo.rv = reflect.ValueOf(^toInt64(runInfo.rv)) + case "!": + if toBool(runInfo.rv) { + runInfo.rv = falseValue + } else { + runInfo.rv = trueValue + } + default: + runInfo.err = newStringError(expr, "unknown operator") + runInfo.rv = nilValue + } + + // ParenExpr + case *ast.ParenExpr: + runInfo.expr = expr.SubExpr + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + + // MemberExpr + case *ast.MemberExpr: + runInfo.expr = expr.Expr + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + + if runInfo.rv.Kind() == reflect.Interface && !runInfo.rv.IsNil() { + runInfo.rv = runInfo.rv.Elem() + } + + if env, ok := runInfo.rv.Interface().(*env.Env); ok { + runInfo.rv, runInfo.err = env.GetValue(expr.Name) + if runInfo.err != nil { + runInfo.err = newError(expr, runInfo.err) + runInfo.rv = nilValue + } + return + } + + value := runInfo.rv.MethodByName(expr.Name) + if value.IsValid() { + runInfo.rv = value + return + } + + if runInfo.rv.Kind() == reflect.Ptr { + runInfo.rv = runInfo.rv.Elem() + } + + switch runInfo.rv.Kind() { + case reflect.Struct: + field, found := runInfo.rv.Type().FieldByName(expr.Name) + if found { + runInfo.rv = runInfo.rv.FieldByIndex(field.Index) + return + } + if runInfo.rv.CanAddr() { + runInfo.rv = runInfo.rv.Addr() + method, found := runInfo.rv.Type().MethodByName(expr.Name) + if found { + runInfo.rv = runInfo.rv.Method(method.Index) + return + } + } + runInfo.err = newStringError(expr, "no member named '"+expr.Name+"' for struct") + runInfo.rv = nilValue + case reflect.Map: + runInfo.rv = getMapIndex(reflect.ValueOf(expr.Name), runInfo.rv) + default: + runInfo.err = newStringError(expr, "type "+runInfo.rv.Kind().String()+" does not support member operation") + runInfo.rv = nilValue + } + + // ItemExpr + case *ast.ItemExpr: + runInfo.expr = expr.Item + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + item := runInfo.rv + + runInfo.expr = expr.Index + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + + if item.Kind() == reflect.Interface && !item.IsNil() { + item = item.Elem() + } + + switch item.Kind() { + case reflect.String, reflect.Slice, reflect.Array: + var index int + index, runInfo.err = tryToInt(runInfo.rv) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "index must be a number") + runInfo.rv = nilValue + return + } + if index < 0 || index >= item.Len() { + runInfo.err = newStringError(expr, "index out of range") + runInfo.rv = nilValue + return + } + if item.Kind() != reflect.String { + runInfo.rv = item.Index(index) + } else { + // String + runInfo.rv = item.Index(index).Convert(stringType) + } + case reflect.Map: + runInfo.rv = getMapIndex(runInfo.rv, item) + default: + runInfo.err = newStringError(expr, "type "+item.Kind().String()+" does not support index operation") + runInfo.rv = nilValue + } + + // SliceExpr + case *ast.SliceExpr: + runInfo.expr = expr.Item + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + item := runInfo.rv + + if item.Kind() == reflect.Interface && !item.IsNil() { + item = item.Elem() + } + + switch item.Kind() { + case reflect.String, reflect.Slice, reflect.Array: + var beginIndex int + endIndex := item.Len() + + if expr.Begin != nil { + runInfo.expr = expr.Begin + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + beginIndex, runInfo.err = tryToInt(runInfo.rv) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "index must be a number") + runInfo.rv = nilValue + return + } + // (0 <= low) <= high <= len(a) + if beginIndex < 0 { + runInfo.err = newStringError(expr, "index out of range") + runInfo.rv = nilValue + return + } + } + + if expr.End != nil { + runInfo.expr = expr.End + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + endIndex, runInfo.err = tryToInt(runInfo.rv) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "index must be a number") + runInfo.rv = nilValue + return + } + // 0 <= low <= (high <= len(a)) + if endIndex > item.Len() { + runInfo.err = newStringError(expr, "index out of range") + runInfo.rv = nilValue + return + } + } + + // 0 <= (low <= high) <= len(a) + if beginIndex > endIndex { + runInfo.err = newStringError(expr, "index out of range") + runInfo.rv = nilValue + return + } + + if item.Kind() == reflect.String { + if expr.Cap != nil { + runInfo.err = newStringError(expr, "type string does not support cap") + runInfo.rv = nilValue + return + } + runInfo.rv = item.Slice(beginIndex, endIndex) + return + } + + sliceCap := item.Cap() + if expr.Cap != nil { + runInfo.expr = expr.Cap + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + sliceCap, runInfo.err = tryToInt(runInfo.rv) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "cap must be a number") + runInfo.rv = nilValue + return + } + // 0 <= low <= (high <= max <= cap(a)) + if sliceCap < endIndex || sliceCap > item.Cap() { + runInfo.err = newStringError(expr, "cap out of range") + runInfo.rv = nilValue + return + } + } + + runInfo.rv = item.Slice3(beginIndex, endIndex, sliceCap) + + default: + runInfo.err = newStringError(expr, "type "+item.Kind().String()+" does not support slice operation") + runInfo.rv = nilValue + } + + // LetsExpr + case *ast.LetsExpr: + var i int + for i, runInfo.expr = range expr.RHSS { + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + if runInfo.rv.Kind() == reflect.Interface && !runInfo.rv.IsNil() { + runInfo.rv = runInfo.rv.Elem() + } + if i < len(expr.LHSS) { + runInfo.expr = expr.LHSS[i] + runInfo.invokeLetExpr() + if runInfo.err != nil { + return + } + } + + } + + // TernaryOpExpr + case *ast.TernaryOpExpr: + runInfo.expr = expr.Expr + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + + if toBool(runInfo.rv) { + runInfo.expr = expr.LHS + } else { + runInfo.expr = expr.RHS + } + runInfo.invokeExpr() + + // NilCoalescingOpExpr + case *ast.NilCoalescingOpExpr: + // if left side has no error and is not nil, returns left side + // otherwise returns right side + runInfo.expr = expr.LHS + runInfo.invokeExpr() + if runInfo.err == nil { + if !isNil(runInfo.rv) { + return + } + } else { + runInfo.err = nil + } + runInfo.expr = expr.RHS + runInfo.invokeExpr() + + // LenExpr + case *ast.LenExpr: + runInfo.expr = expr.Expr + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + + if runInfo.rv.Kind() == reflect.Interface && !runInfo.rv.IsNil() { + runInfo.rv = runInfo.rv.Elem() + } + + switch runInfo.rv.Kind() { + case reflect.Slice, reflect.Array, reflect.Map, reflect.String, reflect.Chan: + runInfo.rv = reflect.ValueOf(int64(runInfo.rv.Len())) + default: + runInfo.err = newStringError(expr, "type "+runInfo.rv.Kind().String()+" does not support len operation") + runInfo.rv = nilValue + } + + // ImportExpr + case *ast.ImportExpr: + runInfo.expr = expr.Name + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + runInfo.rv, runInfo.err = convertReflectValueToType(runInfo.rv, stringType) + if runInfo.err != nil { + runInfo.rv = nilValue + return + } + name := runInfo.rv.String() + runInfo.rv = nilValue + + methods, ok := env.Packages[name] + if !ok { + runInfo.err = newStringError(expr, "package not found: "+name) + return + } + var err error + pack := runInfo.env.NewEnv() + for methodName, methodValue := range methods { + err = pack.DefineValue(methodName, methodValue) + if err != nil { + runInfo.err = newStringError(expr, "import DefineValue error: "+err.Error()) + return + } + } + + types, ok := env.PackageTypes[name] + if ok { + for typeName, typeValue := range types { + err = pack.DefineReflectType(typeName, typeValue) + if err != nil { + runInfo.err = newStringError(expr, "import DefineReflectType error: "+err.Error()) + return + } + } + } + + runInfo.rv = reflect.ValueOf(pack) + + // MakeExpr + case *ast.MakeExpr: + t := makeType(runInfo, expr.TypeData) + if runInfo.err != nil { + runInfo.rv = nilValue + return + } + if t == nil { + runInfo.err = newStringError(expr, "cannot make type nil") + runInfo.rv = nilValue + return + } + + switch expr.TypeData.Kind { + case ast.TypeSlice: + aLen := 0 + if expr.LenExpr != nil { + runInfo.expr = expr.LenExpr + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + aLen = toInt(runInfo.rv) + } + cap := aLen + if expr.CapExpr != nil { + runInfo.expr = expr.CapExpr + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + cap = toInt(runInfo.rv) + } + if aLen > cap { + runInfo.err = newStringError(expr, "make slice len > cap") + runInfo.rv = nilValue + return + } + runInfo.rv = reflect.MakeSlice(t, aLen, cap) + return + case ast.TypeChan: + aLen := 0 + if expr.LenExpr != nil { + runInfo.expr = expr.LenExpr + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + aLen = toInt(runInfo.rv) + } + runInfo.rv = reflect.MakeChan(t, aLen) + return + } + + runInfo.rv, runInfo.err = makeValue(t) + + // MakeTypeExpr + case *ast.MakeTypeExpr: + runInfo.expr = expr.Type + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + + // if expr.Name has a dot in it, it should give a syntax error, so no needs to check err + runInfo.env.DefineReflectType(expr.Name, runInfo.rv.Type()) + + runInfo.rv = reflect.ValueOf(runInfo.rv.Type()) + + // ChanExpr + case *ast.ChanExpr: + runInfo.expr = expr.RHS + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + if runInfo.rv.Kind() == reflect.Interface && !runInfo.rv.IsNil() { + runInfo.rv = runInfo.rv.Elem() + } + + var lhs reflect.Value + rhs := runInfo.rv + + if expr.LHS == nil { + // lhs is nil + if rhs.Kind() != reflect.Chan { + // rhs is not channel + runInfo.err = newStringError(expr, "receive from non-chan type "+rhs.Kind().String()) + runInfo.rv = nilValue + return + } + } else { + // lhs is not nil + runInfo.expr = expr.LHS + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + if runInfo.rv.Kind() == reflect.Interface && !runInfo.rv.IsNil() { + runInfo.rv = runInfo.rv.Elem() + } + if runInfo.rv.Kind() != reflect.Chan { + // lhs is not channel + // lhs <- chan rhs or lhs <- rhs + runInfo.err = newStringError(expr, "send to non-chan type "+runInfo.rv.Kind().String()) + runInfo.rv = nilValue + return + } + lhs = runInfo.rv + } + + var chosen int + var ok bool + + if rhs.Kind() == reflect.Chan { + // rhs is channel + // receive from rhs channel + cases := []reflect.SelectCase{{ + Dir: reflect.SelectRecv, + Chan: reflect.ValueOf(runInfo.ctx.Done()), + }, { + Dir: reflect.SelectRecv, + Chan: rhs, + }} + chosen, runInfo.rv, ok = reflect.Select(cases) + if chosen == 0 { + runInfo.err = ErrInterrupt + runInfo.rv = nilValue + return + } + if !ok { + runInfo.rv = nilValue + return + } + rhs = runInfo.rv + } + + if expr.LHS == nil { + // <- chan rhs is receive + return + } + + // chan lhs <- chan rhs is receive & send + // or + // chan lhs <- rhs is send + + runInfo.rv = nilValue + rhs, runInfo.err = convertReflectValueToType(rhs, lhs.Type().Elem()) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "cannot use type "+rhs.Type().String()+" as type "+lhs.Type().Elem().String()+" to send to chan") + return + } + // send rhs to lhs channel + cases := []reflect.SelectCase{{ + Dir: reflect.SelectRecv, + Chan: reflect.ValueOf(runInfo.ctx.Done()), + }, { + Dir: reflect.SelectSend, + Chan: lhs, + Send: rhs, + }} + if !runInfo.options.Debug { + // captures panic + defer recoverFunc(runInfo) + } + chosen, _, _ = reflect.Select(cases) + if chosen == 0 { + runInfo.err = ErrInterrupt + } + + // FuncExpr + case *ast.FuncExpr: + runInfo.expr = expr + runInfo.funcExpr() + + // AnonCallExpr + case *ast.AnonCallExpr: + runInfo.expr = expr + runInfo.anonCallExpr() + + // CallExpr + case *ast.CallExpr: + runInfo.expr = expr + runInfo.callExpr() + + // IncludeExpr + case *ast.IncludeExpr: + runInfo.expr = expr.ItemExpr + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + itemExpr := runInfo.rv + + runInfo.expr = expr.ListExpr + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + + if runInfo.rv.Kind() != reflect.Slice && runInfo.rv.Kind() != reflect.Array { + runInfo.err = newStringError(expr, "second argument must be slice or array; but have "+runInfo.rv.Kind().String()) + runInfo.rv = nilValue + return + } + + for i := 0; i < runInfo.rv.Len(); i++ { + if equal(itemExpr, runInfo.rv.Index(i)) { + runInfo.rv = trueValue + return + } + } + runInfo.rv = falseValue + + default: + runInfo.err = newStringError(expr, "unknown expression") + runInfo.rv = nilValue + } + +} diff --git a/src/tool/run/vm/vmExprFunction.go b/src/tool/run/vm/vmExprFunction.go new file mode 100644 index 0000000..ec06312 --- /dev/null +++ b/src/tool/run/vm/vmExprFunction.go @@ -0,0 +1,478 @@ +package vm + +import ( + "context" + "fmt" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/ast" +) + +// funcExpr creates a function that reflect Call can use. +// When called, it will run runVMFunction, to run the function statements +func (runInfo *runInfoStruct) funcExpr() { + funcExpr := runInfo.expr.(*ast.FuncExpr) + + // create the inTypes needed by reflect.FuncOf + inTypes := make([]reflect.Type, len(funcExpr.Params)+1) + // for runVMFunction first arg is always context + inTypes[0] = contextType + for i := 1; i < len(inTypes); i++ { + inTypes[i] = reflectValueType + } + if funcExpr.VarArg { + inTypes[len(inTypes)-1] = interfaceSliceType + } + // create funcType, output is always slice of reflect.Type with two values + funcType := reflect.FuncOf(inTypes, []reflect.Type{reflectValueType, reflectValueType}, funcExpr.VarArg) + + // for adding env into saved function + envFunc := runInfo.env + + // create a function that can be used by reflect.MakeFunc + // this function is a translator that converts a function call into a vm run + // returns slice of reflect.Type with two values: + // return value of the function and error value of the run + runVMFunction := func(in []reflect.Value) []reflect.Value { + runInfo := runInfoStruct{ctx: in[0].Interface().(context.Context), options: runInfo.options, env: envFunc.NewEnv(), stmt: funcExpr.Stmt, rv: nilValue} + + // add Params to newEnv, except last Params + for i := 0; i < len(funcExpr.Params)-1; i++ { + runInfo.rv = in[i+1].Interface().(reflect.Value) + runInfo.env.DefineValue(funcExpr.Params[i], runInfo.rv) + } + // add last Params to newEnv + if len(funcExpr.Params) > 0 { + if funcExpr.VarArg { + // function is variadic, add last Params to newEnv without convert to Interface and then reflect.Value + runInfo.rv = in[len(funcExpr.Params)] + runInfo.env.DefineValue(funcExpr.Params[len(funcExpr.Params)-1], runInfo.rv) + } else { + // function is not variadic, add last Params to newEnv + runInfo.rv = in[len(funcExpr.Params)].Interface().(reflect.Value) + runInfo.env.DefineValue(funcExpr.Params[len(funcExpr.Params)-1], runInfo.rv) + } + } + + // run function statements + runInfo.runSingleStmt() + if runInfo.err != nil && runInfo.err != ErrReturn { + runInfo.err = newError(funcExpr, runInfo.err) + // return nil value and error + // need to do single reflect.ValueOf because nilValue is already reflect.Value of nil + // need to do double reflect.ValueOf of newError in order to match + return []reflect.Value{reflectValueNilValue, reflect.ValueOf(reflect.ValueOf(newError(funcExpr, runInfo.err)))} + } + + // the reflect.ValueOf of rv is needed to work in the reflect.Value slice + // reflectValueErrorNilValue is already a double reflect.ValueOf + return []reflect.Value{reflect.ValueOf(runInfo.rv), reflectValueErrorNilValue} + } + + // make the reflect.Value function that calls runVMFunction + runInfo.rv = reflect.MakeFunc(funcType, runVMFunction) + + // if function name is not empty, define it in the env + if funcExpr.Name != "" { + runInfo.env.DefineValue(funcExpr.Name, runInfo.rv) + } +} + +// anonCallExpr handles ast.AnonCallExpr which calls a function anonymously +func (runInfo *runInfoStruct) anonCallExpr() { + anonCallExpr := runInfo.expr.(*ast.AnonCallExpr) + + runInfo.expr = anonCallExpr.Expr + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + + if runInfo.rv.Kind() == reflect.Interface && !runInfo.rv.IsNil() { + runInfo.rv = runInfo.rv.Elem() + } + if runInfo.rv.Kind() != reflect.Func { + runInfo.err = newStringError(anonCallExpr, "cannot call type "+runInfo.rv.Kind().String()) + runInfo.rv = nilValue + return + } + + runInfo.expr = &ast.CallExpr{Func: runInfo.rv, SubExprs: anonCallExpr.SubExprs, VarArg: anonCallExpr.VarArg, Go: anonCallExpr.Go} + runInfo.expr.SetPosition(anonCallExpr.Expr.Position()) + runInfo.invokeExpr() +} + +// callExpr handles *ast.CallExpr which calls a function +func (runInfo *runInfoStruct) callExpr() { + // Note that if the function type looks the same as the VM function type, the returned values will probably be wrong + + callExpr := runInfo.expr.(*ast.CallExpr) + + f := callExpr.Func + if !f.IsValid() { + // if function is not valid try to get by function name + f, runInfo.err = runInfo.env.GetValue(callExpr.Name) + if runInfo.err != nil { + runInfo.err = newError(callExpr, runInfo.err) + runInfo.rv = nilValue + return + } + } + + if f.Kind() == reflect.Interface && !f.IsNil() { + f = f.Elem() + } + if f.Kind() != reflect.Func { + runInfo.err = newStringError(callExpr, "cannot call type "+f.Kind().String()) + runInfo.rv = nilValue + return + } + + var rvs []reflect.Value + var args []reflect.Value + var useCallSlice bool + fType := f.Type() + // check if this is a runVMFunction type + isRunVMFunction := checkIfRunVMFunction(fType) + // create/convert the args to the function + args, useCallSlice = runInfo.makeCallArgs(fType, isRunVMFunction, callExpr) + if runInfo.err != nil { + return + } + + if !runInfo.options.Debug { + // captures panic + defer recoverFunc(runInfo) + } + + runInfo.rv = nilValue + + // useCallSlice lets us know to use CallSlice instead of Call because of the format of the args + if useCallSlice { + if callExpr.Go { + go f.CallSlice(args) + return + } + rvs = f.CallSlice(args) + } else { + if callExpr.Go { + go f.Call(args) + return + } + rvs = f.Call(args) + } + + // TOFIX: how VM pointers/addressing work + // Until then, this is a work around to set pointers back to VM variables + // This will probably panic for some functions and/or calls that are variadic + if !isRunVMFunction { + for i, expr := range callExpr.SubExprs { + if addrExpr, ok := expr.(*ast.AddrExpr); ok { + if identExpr, ok := addrExpr.Expr.(*ast.IdentExpr); ok { + runInfo.rv = args[i].Elem() + runInfo.expr = identExpr + runInfo.invokeLetExpr() + } + } + } + } + + // processCallReturnValues to get/convert return values to normal rv form + runInfo.rv, runInfo.err = processCallReturnValues(rvs, isRunVMFunction, true) +} + +// checkIfRunVMFunction checking the number and types of the reflect.Type. +// If it matches the types for a runVMFunction this will return true, otherwise false +func checkIfRunVMFunction(rt reflect.Type) bool { + if rt.NumIn() < 1 || rt.NumOut() != 2 || rt.In(0) != contextType || rt.Out(0) != reflectValueType || rt.Out(1) != reflectValueType { + return false + } + if rt.NumIn() > 1 { + if rt.IsVariadic() { + if rt.In(rt.NumIn()-1) != interfaceSliceType { + return false + } + } else { + if rt.In(rt.NumIn()-1) != reflectValueType { + return false + } + } + for i := 1; i < rt.NumIn()-1; i++ { + if rt.In(i) != reflectValueType { + return false + } + } + } + return true +} + +// makeCallArgs creates the arguments reflect.Value slice for the four different kinds of functions. +// Also returns true if CallSlice should be used on the arguments, or false if Call should be used. +func (runInfo *runInfoStruct) makeCallArgs(rt reflect.Type, isRunVMFunction bool, callExpr *ast.CallExpr) ([]reflect.Value, bool) { + // number of arguments + numInReal := rt.NumIn() + numIn := numInReal + if isRunVMFunction { + // for runVMFunction the first arg is context so does not count against number of SubExprs + numIn-- + } + if numIn < 1 { + // no arguments needed + if isRunVMFunction { + // for runVMFunction first arg is always context + return []reflect.Value{reflect.ValueOf(runInfo.ctx)}, false + } + return []reflect.Value{}, false + } + + // number of expressions + numExprs := len(callExpr.SubExprs) + // checks to short circuit wrong number of arguments + if (!rt.IsVariadic() && !callExpr.VarArg && numIn != numExprs) || + (rt.IsVariadic() && callExpr.VarArg && (numIn < numExprs || numIn > numExprs+1)) || + (rt.IsVariadic() && !callExpr.VarArg && numIn > numExprs+1) || + (!rt.IsVariadic() && callExpr.VarArg && numIn < numExprs) { + runInfo.err = newStringError(callExpr, fmt.Sprintf("function wants %v arguments but received %v", numIn, numExprs)) + runInfo.rv = nilValue + return nil, false + } + if rt.IsVariadic() && rt.In(numInReal-1).Kind() != reflect.Slice && rt.In(numInReal-1).Kind() != reflect.Array { + runInfo.err = newStringError(callExpr, "function is variadic but last parameter is of type "+rt.In(numInReal-1).String()) + runInfo.rv = nilValue + return nil, false + } + + var args []reflect.Value + indexIn := 0 + indexInReal := 0 + indexExpr := 0 + + if numInReal > numExprs { + args = make([]reflect.Value, 0, numInReal) + } else { + args = make([]reflect.Value, 0, numExprs) + } + if isRunVMFunction { + // for runVMFunction first arg is always context + args = append(args, reflect.ValueOf(runInfo.ctx)) + indexInReal++ + } + + // create arguments except the last one + for indexInReal < numInReal-1 && indexExpr < numExprs-1 { + runInfo.expr = callExpr.SubExprs[indexExpr] + runInfo.invokeExpr() + if runInfo.err != nil { + return nil, false + } + if isRunVMFunction { + args = append(args, reflect.ValueOf(runInfo.rv)) + } else { + runInfo.rv, runInfo.err = convertReflectValueToType(runInfo.rv, rt.In(indexInReal)) + if runInfo.err != nil { + runInfo.err = newStringError(callExpr.SubExprs[indexExpr], + "function wants argument type "+rt.In(indexInReal).String()+" but received type "+runInfo.rv.Type().String()) + runInfo.rv = nilValue + return nil, false + } + args = append(args, runInfo.rv) + } + indexIn++ + indexInReal++ + indexExpr++ + } + + if !rt.IsVariadic() && !callExpr.VarArg { + // function is not variadic and call is not variadic + // add last arguments and return + runInfo.expr = callExpr.SubExprs[indexExpr] + runInfo.invokeExpr() + if runInfo.err != nil { + return nil, false + } + if runInfo.err != nil { + return nil, false + } + if isRunVMFunction { + args = append(args, reflect.ValueOf(runInfo.rv)) + } else { + runInfo.rv, runInfo.err = convertReflectValueToType(runInfo.rv, rt.In(indexInReal)) + if runInfo.err != nil { + runInfo.err = newStringError(callExpr.SubExprs[indexExpr], + "function wants argument type "+rt.In(indexInReal).String()+" but received type "+runInfo.rv.Type().String()) + runInfo.rv = nilValue + return nil, false + } + args = append(args, runInfo.rv) + } + return args, false + } + + if !rt.IsVariadic() && callExpr.VarArg { + // function is not variadic and call is variadic + runInfo.expr = callExpr.SubExprs[indexExpr] + runInfo.invokeExpr() + if runInfo.err != nil { + return nil, false + } + if runInfo.rv.Kind() != reflect.Slice && runInfo.rv.Kind() != reflect.Array { + runInfo.err = newStringError(callExpr, "call is variadic but last parameter is of type "+runInfo.rv.Type().String()) + runInfo.rv = nilValue + return nil, false + } + if runInfo.rv.Len() < numIn-indexIn { + runInfo.err = newStringError(callExpr, fmt.Sprintf("function wants %v arguments but received %v", numIn, numExprs+runInfo.rv.Len()-1)) + runInfo.rv = nilValue + return nil, false + } + + indexSlice := 0 + for indexInReal < numInReal { + if isRunVMFunction { + args = append(args, reflect.ValueOf(runInfo.rv.Index(indexSlice))) + } else { + runInfo.rv, runInfo.err = convertReflectValueToType(runInfo.rv.Index(indexSlice), rt.In(indexInReal)) + if runInfo.err != nil { + runInfo.err = newStringError(callExpr.SubExprs[indexExpr], + "function wants argument type "+rt.In(indexInReal).String()+" but received type "+runInfo.rv.Type().String()) + runInfo.rv = nilValue + return nil, false + } + args = append(args, runInfo.rv) + } + indexIn++ + indexInReal++ + indexSlice++ + } + return args, false + } + + // function is variadic and call may or may not be variadic + + if indexExpr == numExprs { + // no more expressions, return what we have and let reflect Call handle if call is variadic or not + return args, false + } + + if numIn > numExprs { + // there are more arguments after this one, so does not matter if call is variadic or not + // add the last argument then return what we have and let reflect Call handle if call is variadic or not + runInfo.expr = callExpr.SubExprs[indexExpr] + runInfo.invokeExpr() + if runInfo.err != nil { + return nil, false + } + if isRunVMFunction { + args = append(args, reflect.ValueOf(runInfo.rv)) + } else { + runInfo.rv, runInfo.err = convertReflectValueToType(runInfo.rv, rt.In(indexInReal)) + if runInfo.err != nil { + runInfo.err = newStringError(callExpr.SubExprs[indexExpr], + "function wants argument type "+rt.In(indexInReal).String()+" but received type "+runInfo.rv.Type().String()) + runInfo.rv = nilValue + return nil, false + } + args = append(args, runInfo.rv) + } + return args, false + } + + if rt.IsVariadic() && !callExpr.VarArg { + // function is variadic and call is not variadic + sliceType := rt.In(numInReal - 1).Elem() + for indexExpr < numExprs { + runInfo.expr = callExpr.SubExprs[indexExpr] + runInfo.invokeExpr() + if runInfo.err != nil { + return nil, false + } + runInfo.rv, runInfo.err = convertReflectValueToType(runInfo.rv, sliceType) + if runInfo.err != nil { + runInfo.err = newStringError(callExpr.SubExprs[indexExpr], + "function wants argument type "+rt.In(indexInReal).String()+" but received type "+runInfo.rv.Type().String()) + runInfo.rv = nilValue + return nil, false + } + args = append(args, runInfo.rv) + indexExpr++ + } + return args, false + + } + + // function is variadic and call is variadic + // the only time we return CallSlice is true + sliceType := rt.In(numInReal - 1) + if sliceType.Kind() == reflect.Interface && !runInfo.rv.IsNil() { + sliceType = sliceType.Elem() + } + runInfo.expr = callExpr.SubExprs[indexExpr] + runInfo.invokeExpr() + if runInfo.err != nil { + return nil, false + } + runInfo.rv, runInfo.err = convertReflectValueToType(runInfo.rv, sliceType) + if runInfo.err != nil { + runInfo.err = newStringError(callExpr.SubExprs[indexExpr], + "function wants argument type "+rt.In(indexInReal).String()+" but received type "+runInfo.rv.Type().String()) + runInfo.rv = nilValue + return nil, false + } + args = append(args, runInfo.rv) + + return args, true +} + +// processCallReturnValues get/converts the values returned from a function call into our normal reflect.Value, error +func processCallReturnValues(rvs []reflect.Value, isRunVMFunction bool, convertToInterfaceSlice bool) (reflect.Value, error) { + // check if it is not runVMFunction + if !isRunVMFunction { + // the function was a Go function, convert to our normal reflect.Value, error + switch len(rvs) { + case 0: + // no return values so return nil reflect.Value and nil error + return nilValue, nil + case 1: + // one return value but need to add nil error + return rvs[0], nil + } + if convertToInterfaceSlice { + // need to convert from a slice of reflect.Value to slice of interface + return reflectValueSlicetoInterfaceSlice(rvs), nil + } + // need to keep as slice of reflect.Value + return reflect.ValueOf(rvs), nil + } + + // is a runVMFunction, expect return in the runVMFunction format + // convertToInterfaceSlice is ignored + // some of the below checks probably can be removed because they are done in checkIfRunVMFunction + + if len(rvs) != 2 { + return nilValue, fmt.Errorf("VM function did not return 2 values but returned %v values", len(rvs)) + } + if rvs[0].Type() != reflectValueType { + return nilValue, fmt.Errorf("VM function value 1 did not return reflect value type but returned %v type", rvs[0].Type().String()) + } + if rvs[1].Type() != reflectValueType { + return nilValue, fmt.Errorf("VM function value 2 did not return reflect value type but returned %v type", rvs[1].Type().String()) + } + + rvError := rvs[1].Interface().(reflect.Value) + if rvError.Type() != errorType && rvError.Type() != vmErrorType { + return nilValue, fmt.Errorf("VM function error type is %v", rvError.Type()) + } + + if rvError.IsNil() { + // no error, so return the normal VM reflect.Value form + return rvs[0].Interface().(reflect.Value), nil + } + + // VM returns two types of errors, check to see which type + if rvError.Type() == vmErrorType { + // convert to VM *Error + return nilValue, rvError.Interface().(*Error) + } + // convert to error + return nilValue, rvError.Interface().(error) +} diff --git a/src/tool/run/vm/vmFunctions_test.go b/src/tool/run/vm/vmFunctions_test.go new file mode 100644 index 0000000..2e239fc --- /dev/null +++ b/src/tool/run/vm/vmFunctions_test.go @@ -0,0 +1,863 @@ +package vm + +import ( + "bytes" + "fmt" + "reflect" + "sync" + "testing" + "time" + + "github.com/surdeus/goblin/src/tool/run/env" +) + +func TestReturns(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `return 1++`, RunError: fmt.Errorf("invalid operation")}, + {Script: `return 1, 1++`, RunError: fmt.Errorf("invalid operation")}, + {Script: `return 1, 2, 1++`, RunError: fmt.Errorf("invalid operation")}, + + {Script: `return`, RunOutput: nil}, + {Script: `return nil`, RunOutput: nil}, + {Script: `return true`, RunOutput: true}, + {Script: `return 1`, RunOutput: int64(1)}, + {Script: `return 1.1`, RunOutput: float64(1.1)}, + {Script: `return "a"`, RunOutput: "a"}, + + {Script: `b()`, Input: map[string]interface{}{"b": func() {}}, RunOutput: nil}, + {Script: `b()`, Input: map[string]interface{}{"b": func() reflect.Value { return reflect.Value{} }}, RunOutput: reflect.Value{}}, + {Script: `b()`, Input: map[string]interface{}{"b": func() interface{} { return nil }}, RunOutput: nil}, + {Script: `b()`, Input: map[string]interface{}{"b": func() bool { return true }}, RunOutput: true}, + {Script: `b()`, Input: map[string]interface{}{"b": func() int32 { return int32(1) }}, RunOutput: int32(1)}, + {Script: `b()`, Input: map[string]interface{}{"b": func() int64 { return int64(1) }}, RunOutput: int64(1)}, + {Script: `b()`, Input: map[string]interface{}{"b": func() float32 { return float32(1.1) }}, RunOutput: float32(1.1)}, + {Script: `b()`, Input: map[string]interface{}{"b": func() float64 { return float64(1.1) }}, RunOutput: float64(1.1)}, + {Script: `b()`, Input: map[string]interface{}{"b": func() string { return "a" }}, RunOutput: "a"}, + + {Script: `b(a)`, Input: map[string]interface{}{"a": reflect.Value{}, "b": func(c reflect.Value) reflect.Value { return c }}, RunOutput: reflect.Value{}, Output: map[string]interface{}{"a": reflect.Value{}}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": nil, "b": func(c interface{}) interface{} { return c }}, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": true, "b": func(c bool) bool { return c }}, RunOutput: true, Output: map[string]interface{}{"a": true}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": int32(1), "b": func(c int32) int32 { return c }}, RunOutput: int32(1), Output: map[string]interface{}{"a": int32(1)}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": int64(1), "b": func(c int64) int64 { return c }}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": float32(1.1), "b": func(c float32) float32 { return c }}, RunOutput: float32(1.1), Output: map[string]interface{}{"a": float32(1.1)}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": float64(1.1), "b": func(c float64) float64 { return c }}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": "a", "b": func(c string) string { return c }}, RunOutput: "a", Output: map[string]interface{}{"a": "a"}}, + + {Script: `b(a)`, Input: map[string]interface{}{"a": "a", "b": func(c bool) bool { return c }}, RunError: fmt.Errorf("function wants argument type bool but received type string"), Output: map[string]interface{}{"a": "a"}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": int64(1), "b": func(c int32) int32 { return c }}, RunOutput: int32(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": int32(1), "b": func(c int64) int64 { return c }}, RunOutput: int64(1), Output: map[string]interface{}{"a": int32(1)}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": float64(1.25), "b": func(c float32) float32 { return c }}, RunOutput: float32(1.25), Output: map[string]interface{}{"a": float64(1.25)}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": float32(1.25), "b": func(c float64) float64 { return c }}, RunOutput: float64(1.25), Output: map[string]interface{}{"a": float32(1.25)}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": true, "b": func(c string) string { return c }}, RunError: fmt.Errorf("function wants argument type string but received type bool"), Output: map[string]interface{}{"a": true}}, + + {Script: `b(a)`, Input: map[string]interface{}{"a": testVarValueBool, "b": func(c interface{}) interface{} { return c }}, RunOutput: testVarValueBool, Output: map[string]interface{}{"a": testVarValueBool}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": testVarValueInt32, "b": func(c interface{}) interface{} { return c }}, RunOutput: testVarValueInt32, Output: map[string]interface{}{"a": testVarValueInt32}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": testVarValueInt64, "b": func(c interface{}) interface{} { return c }}, RunOutput: testVarValueInt64, Output: map[string]interface{}{"a": testVarValueInt64}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": testVarValueFloat32, "b": func(c interface{}) interface{} { return c }}, RunOutput: testVarValueFloat32, Output: map[string]interface{}{"a": testVarValueFloat32}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": testVarValueFloat64, "b": func(c interface{}) interface{} { return c }}, RunOutput: testVarValueFloat64, Output: map[string]interface{}{"a": testVarValueFloat64}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": testVarValueString, "b": func(c interface{}) interface{} { return c }}, RunOutput: testVarValueString, Output: map[string]interface{}{"a": testVarValueString}}, + + {Script: `func aFunc() {}; aFunc()`, RunOutput: nil}, + {Script: `func aFunc() { return }; aFunc()`, RunOutput: nil}, + {Script: `func aFunc() { return }; a = aFunc()`, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + {Script: `func aFunc() { return 1 }; aFunc()`, RunOutput: int64(1)}, + {Script: `func aFunc() { return 1 }; a = aFunc()`, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + + {Script: `func aFunc() {return nil}; aFunc()`, RunOutput: nil}, + {Script: `func aFunc() {return true}; aFunc()`, RunOutput: true}, + {Script: `func aFunc() {return 1}; aFunc()`, RunOutput: int64(1)}, + {Script: `func aFunc() {return 1.1}; aFunc()`, RunOutput: float64(1.1)}, + {Script: `func aFunc() {return "a"}; aFunc()`, RunOutput: "a"}, + + {Script: `func aFunc() {return 1 + 2}; aFunc()`, RunOutput: int64(3)}, + {Script: `func aFunc() {return 1.25 + 2.25}; aFunc()`, RunOutput: float64(3.5)}, + {Script: `func aFunc() {return "a" + "b"}; aFunc()`, RunOutput: "ab"}, + + {Script: `func aFunc() {return 1 + 2, 3 + 4}; aFunc()`, RunOutput: []interface{}{int64(3), int64(7)}}, + {Script: `func aFunc() {return 1.25 + 2.25, 3.25 + 4.25}; aFunc()`, RunOutput: []interface{}{float64(3.5), float64(7.5)}}, + {Script: `func aFunc() {return "a" + "b", "c" + "d"}; aFunc()`, RunOutput: []interface{}{"ab", "cd"}}, + + {Script: `func aFunc() {return nil, nil}; aFunc()`, RunOutput: []interface{}{nil, nil}}, + {Script: `func aFunc() {return true, false}; aFunc()`, RunOutput: []interface{}{true, false}}, + {Script: `func aFunc() {return 1, 2}; aFunc()`, RunOutput: []interface{}{int64(1), int64(2)}}, + {Script: `func aFunc() {return 1.1, 2.2}; aFunc()`, RunOutput: []interface{}{float64(1.1), float64(2.2)}}, + {Script: `func aFunc() {return "a", "b"}; aFunc()`, RunOutput: []interface{}{"a", "b"}}, + + {Script: `func aFunc() {return [nil]}; aFunc()`, RunOutput: []interface{}{nil}}, + {Script: `func aFunc() {return [nil, nil]}; aFunc()`, RunOutput: []interface{}{nil, nil}}, + {Script: `func aFunc() {return [nil, nil, nil]}; aFunc()`, RunOutput: []interface{}{nil, nil, nil}}, + {Script: `func aFunc() {return [nil, nil], [nil, nil]}; aFunc()`, RunOutput: []interface{}{[]interface{}{nil, nil}, []interface{}{nil, nil}}}, + + {Script: `func aFunc() {return [true]}; aFunc()`, RunOutput: []interface{}{true}}, + {Script: `func aFunc() {return [true, false]}; aFunc()`, RunOutput: []interface{}{true, false}}, + {Script: `func aFunc() {return [true, false, true]}; aFunc()`, RunOutput: []interface{}{true, false, true}}, + {Script: `func aFunc() {return [true, false], [false, true]}; aFunc()`, RunOutput: []interface{}{[]interface{}{true, false}, []interface{}{false, true}}}, + + {Script: `func aFunc() {return []}; aFunc()`, RunOutput: []interface{}{}}, + {Script: `func aFunc() {return [1]}; aFunc()`, RunOutput: []interface{}{int64(1)}}, + {Script: `func aFunc() {return [1, 2]}; aFunc()`, RunOutput: []interface{}{int64(1), int64(2)}}, + {Script: `func aFunc() {return [1, 2, 3]}; aFunc()`, RunOutput: []interface{}{int64(1), int64(2), int64(3)}}, + {Script: `func aFunc() {return [1, 2], [3, 4]}; aFunc()`, RunOutput: []interface{}{[]interface{}{int64(1), int64(2)}, []interface{}{int64(3), int64(4)}}}, + + {Script: `func aFunc() {return [1.1]}; aFunc()`, RunOutput: []interface{}{float64(1.1)}}, + {Script: `func aFunc() {return [1.1, 2.2]}; aFunc()`, RunOutput: []interface{}{float64(1.1), float64(2.2)}}, + {Script: `func aFunc() {return [1.1, 2.2, 3.3]}; aFunc()`, RunOutput: []interface{}{float64(1.1), float64(2.2), float64(3.3)}}, + {Script: `func aFunc() {return [1.1, 2.2], [3.3, 4.4]}; aFunc()`, RunOutput: []interface{}{[]interface{}{float64(1.1), float64(2.2)}, []interface{}{float64(3.3), float64(4.4)}}}, + + {Script: `func aFunc() {return ["a"]}; aFunc()`, RunOutput: []interface{}{"a"}}, + {Script: `func aFunc() {return ["a", "b"]}; aFunc()`, RunOutput: []interface{}{"a", "b"}}, + {Script: `func aFunc() {return ["a", "b", "c"]}; aFunc()`, RunOutput: []interface{}{"a", "b", "c"}}, + {Script: `func aFunc() {return ["a", "b"], ["c", "d"]}; aFunc()`, RunOutput: []interface{}{[]interface{}{"a", "b"}, []interface{}{"c", "d"}}}, + + {Script: `func aFunc() {return nil, nil}; aFunc()`, RunOutput: []interface{}{interface{}(nil), interface{}(nil)}}, + {Script: `func aFunc() {return true, false}; aFunc()`, RunOutput: []interface{}{true, false}}, + {Script: `func aFunc() {return 1, 2}; aFunc()`, RunOutput: []interface{}{int64(1), int64(2)}}, + {Script: `func aFunc() {return 1.1, 2.2}; aFunc()`, RunOutput: []interface{}{float64(1.1), float64(2.2)}}, + {Script: `func aFunc() {return "a", "b"}; aFunc()`, RunOutput: []interface{}{"a", "b"}}, + + {Script: `func aFunc() {return a}; aFunc()`, Input: map[string]interface{}{"a": reflect.Value{}}, RunOutput: reflect.Value{}, Output: map[string]interface{}{"a": reflect.Value{}}}, + + {Script: `func aFunc() {return a}; aFunc()`, Input: map[string]interface{}{"a": nil}, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + {Script: `func aFunc() {return a}; aFunc()`, Input: map[string]interface{}{"a": true}, RunOutput: true, Output: map[string]interface{}{"a": true}}, + {Script: `func aFunc() {return a}; aFunc()`, Input: map[string]interface{}{"a": int64(1)}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `func aFunc() {return a}; aFunc()`, Input: map[string]interface{}{"a": float64(1.1)}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `func aFunc() {return a}; aFunc()`, Input: map[string]interface{}{"a": "a"}, RunOutput: "a", Output: map[string]interface{}{"a": "a"}}, + + {Script: `func aFunc() {return a, a}; aFunc()`, Input: map[string]interface{}{"a": reflect.Value{}}, RunOutput: []interface{}{reflect.Value{}, reflect.Value{}}, Output: map[string]interface{}{"a": reflect.Value{}}}, + {Script: `func aFunc() {return a, a}; aFunc()`, Input: map[string]interface{}{"a": nil}, RunOutput: []interface{}{nil, nil}, Output: map[string]interface{}{"a": nil}}, + {Script: `func aFunc() {return a, a}; aFunc()`, Input: map[string]interface{}{"a": true}, RunOutput: []interface{}{true, true}, Output: map[string]interface{}{"a": true}}, + {Script: `func aFunc() {return a, a}; aFunc()`, Input: map[string]interface{}{"a": int32(1)}, RunOutput: []interface{}{int32(1), int32(1)}, Output: map[string]interface{}{"a": int32(1)}}, + {Script: `func aFunc() {return a, a}; aFunc()`, Input: map[string]interface{}{"a": int64(1)}, RunOutput: []interface{}{int64(1), int64(1)}, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `func aFunc() {return a, a}; aFunc()`, Input: map[string]interface{}{"a": float32(1.1)}, RunOutput: []interface{}{float32(1.1), float32(1.1)}, Output: map[string]interface{}{"a": float32(1.1)}}, + {Script: `func aFunc() {return a, a}; aFunc()`, Input: map[string]interface{}{"a": float64(1.1)}, RunOutput: []interface{}{float64(1.1), float64(1.1)}, Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `func aFunc() {return a, a}; aFunc()`, Input: map[string]interface{}{"a": "a"}, RunOutput: []interface{}{"a", "a"}, Output: map[string]interface{}{"a": "a"}}, + + {Script: `func a(x) { return x}; a(nil)`, RunOutput: nil}, + {Script: `func a(x) { return x}; a(true)`, RunOutput: true}, + {Script: `func a(x) { return x}; a(1)`, RunOutput: int64(1)}, + {Script: `func a(x) { return x}; a(1.1)`, RunOutput: float64(1.1)}, + {Script: `func a(x) { return x}; a("a")`, RunOutput: "a"}, + + {Script: `func aFunc() {return a}; for {aFunc(); break}`, Input: map[string]interface{}{"a": nil}, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + {Script: `func aFunc() {return a}; for {aFunc(); break}`, Input: map[string]interface{}{"a": true}, RunOutput: nil, Output: map[string]interface{}{"a": true}}, + {Script: `func aFunc() {return a}; for {aFunc(); break}`, Input: map[string]interface{}{"a": int64(1)}, RunOutput: nil, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `func aFunc() {return a}; for {aFunc(); break}`, Input: map[string]interface{}{"a": float64(1.1)}, RunOutput: nil, Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `func aFunc() {return a}; for {aFunc(); break}`, Input: map[string]interface{}{"a": "a"}, RunOutput: nil, Output: map[string]interface{}{"a": "a"}}, + + {Script: `func aFunc() {for {return a}}; aFunc()`, Input: map[string]interface{}{"a": nil}, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + {Script: `func aFunc() {for {return a}}; aFunc()`, Input: map[string]interface{}{"a": true}, RunOutput: true, Output: map[string]interface{}{"a": true}}, + {Script: `func aFunc() {for {return a}}; aFunc()`, Input: map[string]interface{}{"a": int64(1)}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `func aFunc() {for {return a}}; aFunc()`, Input: map[string]interface{}{"a": float64(1.1)}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `func aFunc() {for {return a}}; aFunc()`, Input: map[string]interface{}{"a": "a"}, RunOutput: "a", Output: map[string]interface{}{"a": "a"}}, + + {Script: `func aFunc() {for {if true {return a}}}; aFunc()`, Input: map[string]interface{}{"a": nil}, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + {Script: `func aFunc() {for {if true {return a}}}; aFunc()`, Input: map[string]interface{}{"a": true}, RunOutput: true, Output: map[string]interface{}{"a": true}}, + {Script: `func aFunc() {for {if true {return a}}}; aFunc()`, Input: map[string]interface{}{"a": int64(1)}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `func aFunc() {for {if true {return a}}}; aFunc()`, Input: map[string]interface{}{"a": float64(1.1)}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `func aFunc() {for {if true {return a}}}; aFunc()`, Input: map[string]interface{}{"a": "a"}, RunOutput: "a", Output: map[string]interface{}{"a": "a"}}, + + {Script: `func aFunc() {return nil, nil}; a, b = aFunc()`, RunOutput: nil, Output: map[string]interface{}{"a": nil, "b": nil}}, + {Script: `func aFunc() {return true, false}; a, b = aFunc()`, RunOutput: false, Output: map[string]interface{}{"a": true, "b": false}}, + {Script: `func aFunc() {return 1, 2}; a, b = aFunc()`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(1), "b": int64(2)}}, + {Script: `func aFunc() {return 1.1, 2.2}; a, b = aFunc()`, RunOutput: float64(2.2), Output: map[string]interface{}{"a": float64(1.1), "b": float64(2.2)}}, + {Script: `func aFunc() {return "a", "b"}; a, b = aFunc()`, RunOutput: "b", Output: map[string]interface{}{"a": "a", "b": "b"}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestFunctions(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `a()`, Input: map[string]interface{}{"a": reflect.Value{}}, RunError: fmt.Errorf("cannot call type struct")}, + {Script: `a = nil; a()`, RunError: fmt.Errorf("cannot call type interface"), Output: map[string]interface{}{"a": nil}}, + {Script: `a = true; a()`, RunError: fmt.Errorf("cannot call type bool"), Output: map[string]interface{}{"a": true}}, + {Script: `a = nil; b = func c(d) { return d == nil }; c = nil; c(a)`, RunError: fmt.Errorf("cannot call type interface"), Output: map[string]interface{}{"a": nil}}, + {Script: `a = [true]; a()`, RunError: fmt.Errorf("cannot call type slice")}, + {Script: `a = [true]; func b(c) { return c() }; b(a)`, RunError: fmt.Errorf("cannot call type slice")}, + {Script: `a = {}; a.missing()`, RunError: fmt.Errorf("cannot call type interface"), Output: map[string]interface{}{"a": map[interface{}]interface{}{}}}, + {Script: `a = 1; b = func(,a){}; a`, ParseError: fmt.Errorf("syntax error: unexpected ','"), RunOutput: int64(1)}, + + {Script: `func a(b) { }; a()`, RunError: fmt.Errorf("function wants 1 arguments but received 0")}, + {Script: `func a(b) { }; a(true, true)`, RunError: fmt.Errorf("function wants 1 arguments but received 2")}, + {Script: `func a(b, c) { }; a()`, RunError: fmt.Errorf("function wants 2 arguments but received 0")}, + {Script: `func a(b, c) { }; a(true)`, RunError: fmt.Errorf("function wants 2 arguments but received 1")}, + {Script: `func a(b, c) { }; a(true, true, true)`, RunError: fmt.Errorf("function wants 2 arguments but received 3")}, + + {Script: `func a() { return "a" }; a.b()`, RunError: fmt.Errorf("type func does not support member operation")}, + {Script: `a = [func () { return nil}]; func b(c) { return c() }; b(a[1])`, RunError: fmt.Errorf("index out of range")}, + {Script: `func a() { return "a" }; b()`, RunError: fmt.Errorf("undefined symbol 'b'")}, + {Script: ` func a() { return "a" }; 1++()`, RunError: fmt.Errorf("invalid operation")}, + {Script: ` func a(b) { return b }; a(1++)`, RunError: fmt.Errorf("invalid operation")}, + + {Script: `a`, Input: map[string]interface{}{"a": testVarFunc}, RunOutput: testVarFunc, Output: map[string]interface{}{"a": testVarFunc}}, + {Script: `a()`, Input: map[string]interface{}{"a": testVarFunc}, RunOutput: int64(1), Output: map[string]interface{}{"a": testVarFunc}}, + {Script: `a`, Input: map[string]interface{}{"a": testVarFuncP}, RunOutput: testVarFuncP, Output: map[string]interface{}{"a": testVarFuncP}}, + // TOFIX: + // {Script: `a()`, Input: map[string]interface{}{"a": testVarFuncP}, RunOutput: int64(1), Output: map[string]interface{}{"a": testVarFuncP}}, + + {Script: `module a { func b() { return } }; a.b()`, RunOutput: nil}, + {Script: `module a { func b() { return nil} }; a.b()`, RunOutput: nil}, + {Script: `module a { func b() { return true} }; a.b()`, RunOutput: true}, + {Script: `module a { func b() { return 1} }; a.b()`, RunOutput: int64(1)}, + {Script: `module a { func b() { return 1.1} }; a.b()`, RunOutput: float64(1.1)}, + {Script: `module a { func b() { return "a"} }; a.b()`, RunOutput: "a"}, + + {Script: `if true { module a { func b() { return } } }; a.b()`, RunError: fmt.Errorf("undefined symbol 'a'")}, + + {Script: `a = 1; func b() { a = 2 }; b()`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `b(a); a`, Input: map[string]interface{}{"a": int64(1), "b": func(c interface{}) { c = int64(2); _ = c }}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `func b() { }; go b()`, RunOutput: nil}, + + {Script: `b(a)`, Input: map[string]interface{}{"a": nil, "b": func(c interface{}) bool { return c == nil }}, RunOutput: true, Output: map[string]interface{}{"a": nil}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": true, "b": func(c bool) bool { return c == true }}, RunOutput: true, Output: map[string]interface{}{"a": true}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": int32(1), "b": func(c int32) bool { return c == 1 }}, RunOutput: true, Output: map[string]interface{}{"a": int32(1)}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": int64(1), "b": func(c int64) bool { return c == 1 }}, RunOutput: true, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": float32(1.1), "b": func(c float32) bool { return c == 1.1 }}, RunOutput: true, Output: map[string]interface{}{"a": float32(1.1)}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": float64(1.1), "b": func(c float64) bool { return c == 1.1 }}, RunOutput: true, Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": "a", "b": func(c string) bool { return c == "a" }}, RunOutput: true, Output: map[string]interface{}{"a": "a"}}, + + {Script: `b(a)`, Input: map[string]interface{}{"a": testVarValueBool, "b": func(c reflect.Value) bool { return c == testVarValueBool }}, RunOutput: true, Output: map[string]interface{}{"a": testVarValueBool}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": testVarValueInt32, "b": func(c reflect.Value) bool { return c == testVarValueInt32 }}, RunOutput: true, Output: map[string]interface{}{"a": testVarValueInt32}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": testVarValueInt64, "b": func(c reflect.Value) bool { return c == testVarValueInt64 }}, RunOutput: true, Output: map[string]interface{}{"a": testVarValueInt64}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": testVarValueFloat32, "b": func(c reflect.Value) bool { return c == testVarValueFloat32 }}, RunOutput: true, Output: map[string]interface{}{"a": testVarValueFloat32}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": testVarValueFloat64, "b": func(c reflect.Value) bool { return c == testVarValueFloat64 }}, RunOutput: true, Output: map[string]interface{}{"a": testVarValueFloat64}}, + {Script: `b(a)`, Input: map[string]interface{}{"a": testVarValueString, "b": func(c reflect.Value) bool { return c == testVarValueString }}, RunOutput: true, Output: map[string]interface{}{"a": testVarValueString}}, + + {Script: `x(a, b, c, d, e, f, g)`, Input: map[string]interface{}{"a": nil, "b": true, "c": int32(1), "d": int64(2), "e": float32(1.1), "f": float64(2.2), "g": "g", + "x": func(a interface{}, b bool, c int32, d int64, e float32, f float64, g string) bool { + return a == nil && b == true && c == 1 && d == 2 && e == 1.1 && f == 2.2 && g == "g" + }}, RunOutput: true, Output: map[string]interface{}{"a": nil, "b": true, "c": int32(1), "d": int64(2), "e": float32(1.1), "f": float64(2.2), "g": "g"}}, + {Script: `x(a, b, c, d, e, f, g)`, Input: map[string]interface{}{"a": nil, "b": true, "c": int32(1), "d": int64(2), "e": float32(1.1), "f": float64(2.2), "g": "g", + "x": func(a interface{}, b bool, c int32, d int64, e float32, f float64, g string) (interface{}, bool, int32, int64, float32, float64, string) { + return a, b, c, d, e, f, g + }}, RunOutput: []interface{}{nil, true, int32(1), int64(2), float32(1.1), float64(2.2), "g"}, Output: map[string]interface{}{"a": nil, "b": true, "c": int32(1), "d": int64(2), "e": float32(1.1), "f": float64(2.2), "g": "g"}}, + + {Script: `b = a()`, Input: map[string]interface{}{"a": func() (bool, int32, int64, float32, float64, string) { return true, 1, 2, 3.3, 4.4, "5" }}, RunOutput: []interface{}{true, int32(1), int64(2), float32(3.3), float64(4.4), "5"}, Output: map[string]interface{}{"b": []interface{}{true, int32(1), int64(2), float32(3.3), float64(4.4), "5"}}}, + {Script: `b = a(); b`, Input: map[string]interface{}{"a": func() (bool, int32, int64, float32, float64, string) { return true, 1, 2, 3.3, 4.4, "5" }}, RunOutput: []interface{}{true, int32(1), int64(2), float32(3.3), float64(4.4), "5"}, Output: map[string]interface{}{"b": []interface{}{true, int32(1), int64(2), float32(3.3), float64(4.4), "5"}}}, + {Script: `b, c = a(); b`, Input: map[string]interface{}{"a": func() (bool, int32, int64, float32, float64, string) { return true, 1, 2, 3.3, 4.4, "5" }}, RunOutput: true, Output: map[string]interface{}{"b": true, "c": int32(1)}}, + {Script: `b, c, d = a(); b`, Input: map[string]interface{}{"a": func() (bool, int32, int64, float32, float64, string) { return true, 1, 2, 3.3, 4.4, "5" }}, RunOutput: true, Output: map[string]interface{}{"b": true, "c": int32(1), "d": int64(2)}}, + {Script: `b, c, d, e = a(); b`, Input: map[string]interface{}{"a": func() (bool, int32, int64, float32, float64, string) { return true, 1, 2, 3.3, 4.4, "5" }}, RunOutput: true, Output: map[string]interface{}{"b": true, "c": int32(1), "d": int64(2), "e": float32(3.3)}}, + {Script: `b, c, d, e, f = a(); b`, Input: map[string]interface{}{"a": func() (bool, int32, int64, float32, float64, string) { return true, 1, 2, 3.3, 4.4, "5" }}, RunOutput: true, Output: map[string]interface{}{"b": true, "c": int32(1), "d": int64(2), "e": float32(3.3), "f": float64(4.4)}}, + {Script: `b, c, d, e, f, g = a(); b`, Input: map[string]interface{}{"a": func() (bool, int32, int64, float32, float64, string) { return true, 1, 2, 3.3, 4.4, "5" }}, RunOutput: true, Output: map[string]interface{}{"b": true, "c": int32(1), "d": int64(2), "e": float32(3.3), "f": float64(4.4), "g": "5"}}, + + {Script: `a = nil; b(a)`, Input: map[string]interface{}{"b": func(c interface{}) bool { return c == nil }}, RunOutput: true, Output: map[string]interface{}{"a": nil}}, + {Script: `a = true; b(a)`, Input: map[string]interface{}{"b": func(c bool) bool { return c == true }}, RunOutput: true, Output: map[string]interface{}{"a": true}}, + {Script: `a = 1; b(a)`, Input: map[string]interface{}{"b": func(c int64) bool { return c == 1 }}, RunOutput: true, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1.1; b(a)`, Input: map[string]interface{}{"b": func(c float64) bool { return c == 1.1 }}, RunOutput: true, Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `a = "a"; b(a)`, Input: map[string]interface{}{"b": func(c string) bool { return c == "a" }}, RunOutput: true, Output: map[string]interface{}{"a": "a"}}, + + {Script: `func b(c) { return c == nil }; b(a)`, Input: map[string]interface{}{"a": nil}, RunOutput: true, Output: map[string]interface{}{"a": nil}}, + {Script: `func b(c) { return c == true }; b(a)`, Input: map[string]interface{}{"a": true}, RunOutput: true, Output: map[string]interface{}{"a": true}}, + {Script: `func b(c) { return c == 1 }; b(a)`, Input: map[string]interface{}{"a": int32(1)}, RunOutput: true, Output: map[string]interface{}{"a": int32(1)}}, + {Script: `func b(c) { return c == 1 }; b(a)`, Input: map[string]interface{}{"a": int64(1)}, RunOutput: true, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `func b(c) { return c == 1.1 }; b(a)`, Input: map[string]interface{}{"a": float32(1.1)}, RunOutput: true, Output: map[string]interface{}{"a": float32(1.1)}}, + {Script: `func b(c) { return c == 1.1 }; b(a)`, Input: map[string]interface{}{"a": float64(1.1)}, RunOutput: true, Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `func b(c) { return c == "a" }; b(a)`, Input: map[string]interface{}{"a": "a"}, RunOutput: true, Output: map[string]interface{}{"a": "a"}}, + + {Script: `a = nil; func b(c) { return c == nil }; b(a)`, RunOutput: true, Output: map[string]interface{}{"a": nil}}, + {Script: `a = true; func b(c) { return c == true }; b(a)`, RunOutput: true, Output: map[string]interface{}{"a": true}}, + {Script: `a = 1; func b(c) { return c == 1 }; b(a)`, Input: map[string]interface{}{"a": int64(1)}, RunOutput: true, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1.1; func b(c) { return c == 1.1 }; b(a)`, Input: map[string]interface{}{"a": float64(1.1)}, RunOutput: true, Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `a = "a"; func b(c) { return c == "a" }; b(a)`, Input: map[string]interface{}{"a": "a"}, RunOutput: true, Output: map[string]interface{}{"a": "a"}}, + + {Script: `b(a[0])`, Input: map[string]interface{}{"a": []interface{}{nil}, "b": func(c interface{}) bool { return c == nil }}, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{nil}}}, + {Script: `b(a[0])`, Input: map[string]interface{}{"a": []interface{}{true}, "b": func(c interface{}) bool { return c == true }}, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{true}}}, + {Script: `b(a[0])`, Input: map[string]interface{}{"a": []interface{}{int32(1)}, "b": func(c interface{}) bool { return c == int32(1) }}, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{int32(1)}}}, + {Script: `b(a[0])`, Input: map[string]interface{}{"a": []interface{}{int64(1)}, "b": func(c interface{}) bool { return c == int64(1) }}, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{int64(1)}}}, + {Script: `b(a[0])`, Input: map[string]interface{}{"a": []interface{}{float32(1.1)}, "b": func(c interface{}) bool { return c == float32(1.1) }}, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{float32(1.1)}}}, + {Script: `b(a[0])`, Input: map[string]interface{}{"a": []interface{}{float64(1.1)}, "b": func(c interface{}) bool { return c == float64(1.1) }}, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{float64(1.1)}}}, + {Script: `b(a[0])`, Input: map[string]interface{}{"a": []interface{}{"a"}, "b": func(c interface{}) bool { return c == "a" }}, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{"a"}}}, + + // TOFIX: + // {Script: `b(a)`, + // Input: map[string]interface{}{"a": []bool{true, false, true}, "b": func(c ...bool) bool { return c[len(c)-1] }}, + // RunOutput: true, Output: map[string]interface{}{"a": true}}, + + {Script: `b(a[0])`, Input: map[string]interface{}{"a": []interface{}{true}, "b": func(c bool) bool { return c == true }}, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{true}}}, + {Script: `b(a[0])`, Input: map[string]interface{}{"a": []interface{}{int32(1)}, "b": func(c int32) bool { return c == int32(1) }}, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{int32(1)}}}, + {Script: `b(a[0])`, Input: map[string]interface{}{"a": []interface{}{int64(1)}, "b": func(c int64) bool { return c == int64(1) }}, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{int64(1)}}}, + {Script: `b(a[0])`, Input: map[string]interface{}{"a": []interface{}{float32(1.1)}, "b": func(c float32) bool { return c == float32(1.1) }}, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{float32(1.1)}}}, + {Script: `b(a[0])`, Input: map[string]interface{}{"a": []interface{}{float64(1.1)}, "b": func(c float64) bool { return c == float64(1.1) }}, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{float64(1.1)}}}, + {Script: `b(a[0])`, Input: map[string]interface{}{"a": []interface{}{"a"}, "b": func(c string) bool { return c == "a" }}, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{"a"}}}, + + {Script: `a = [nil]; b(a[0])`, Input: map[string]interface{}{"b": func(c interface{}) bool { return c == nil }}, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{nil}}}, + {Script: `a = [true]; b(a[0])`, Input: map[string]interface{}{"b": func(c bool) bool { return c == true }}, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{true}}}, + {Script: `a = [1]; b(a[0])`, Input: map[string]interface{}{"b": func(c int64) bool { return c == int64(1) }}, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{int64(1)}}}, + {Script: `a = [1.1]; b(a[0])`, Input: map[string]interface{}{"b": func(c float64) bool { return c == float64(1.1) }}, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{float64(1.1)}}}, + {Script: `a = ["a"]; b(a[0])`, Input: map[string]interface{}{"b": func(c string) bool { return c == "a" }}, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{"a"}}}, + + {Script: `a = [nil]; func b(c) { c == nil }; b(a[0])`, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{nil}}}, + {Script: `a = [true]; func b(c) { c == true }; b(a[0])`, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{true}}}, + {Script: `a = [1]; func b(c) { c == 1 }; b(a[0])`, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{int64(1)}}}, + {Script: `a = [1.1]; func b(c) { c == 1.1 }; b(a[0])`, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{float64(1.1)}}}, + {Script: `a = ["a"]; func b(c) { c == "a" }; b(a[0])`, RunOutput: true, Output: map[string]interface{}{"a": []interface{}{"a"}}}, + + {Script: `a = nil; b = func (d) { return d == nil }; b(a)`, RunOutput: true, Output: map[string]interface{}{"a": nil}}, + {Script: `a = true; b = func (d) { return d == true }; b(a)`, RunOutput: true, Output: map[string]interface{}{"a": true}}, + {Script: `a = 1; b = func (d) { return d == 1 }; b(a)`, RunOutput: true, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1.1; b = func (d) { return d == 1.1 }; b(a)`, RunOutput: true, Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `a = "a"; b = func (d) { return d == "a" }; b(a)`, RunOutput: true, Output: map[string]interface{}{"a": "a"}}, + + {Script: `a = nil; b = func c(d) { return d == nil }; b(a)`, RunOutput: true, Output: map[string]interface{}{"a": nil}}, + {Script: `a = true; b = func c(d) { return d == true }; b(a)`, RunOutput: true, Output: map[string]interface{}{"a": true}}, + {Script: `a = 1; b = func c(d) { return d == 1 }; b(a)`, RunOutput: true, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1.1; b = func c(d) { return d == 1.1 }; b(a)`, RunOutput: true, Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `a = "a"; b = func c(d) { return d == "a" }; b(a)`, RunOutput: true, Output: map[string]interface{}{"a": "a"}}, + + {Script: `a = nil; b = func c(d) { return d == nil }; c(a)`, RunOutput: true, Output: map[string]interface{}{"a": nil}}, + {Script: `a = true; b = func c(d) { return d == true }; c(a)`, RunOutput: true, Output: map[string]interface{}{"a": true}}, + {Script: `a = 1; b = func c(d) { return d == 1 }; c(a)`, RunOutput: true, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1.1; b = func c(d) { return d == 1.1 }; c(a)`, RunOutput: true, Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `a = "a"; b = func c(d) { return d == "a" }; c(a)`, RunOutput: true, Output: map[string]interface{}{"a": "a"}}, + + {Script: `a = nil; func b() { return func c(d) { d == nil } }; e = b(); e(a)`, RunOutput: true, Output: map[string]interface{}{"a": nil}}, + {Script: `a = true; func b() { return func c(d) { d == true } }; e = b(); e(a)`, RunOutput: true, Output: map[string]interface{}{"a": true}}, + {Script: `a = 1; func b() { return func c(d) { d == 1 } }; e = b(); e(a)`, RunOutput: true, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1.1; func b() { return func c(d) { d == 1.1 } }; e = b(); e(a)`, RunOutput: true, Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `a = "a"; func b() { return func c(d) { d == "a" } }; e = b(); e(a)`, RunOutput: true, Output: map[string]interface{}{"a": "a"}}, + + {Script: `a = func () { return nil }; func b(c) { return c() }; b(a)`, RunOutput: nil}, + {Script: `a = func () { return true }; func b(c) { return c() }; b(a)`, RunOutput: true}, + {Script: `a = func () { return 1 }; func b(c) { return c() }; b(a)`, RunOutput: int64(1)}, + {Script: `a = func () { return 1.1 }; func b(c) { return c() }; b(a)`, RunOutput: float64(1.1)}, + {Script: `a = func () { return "a" }; func b(c) { return c() }; b(a)`, RunOutput: "a"}, + + {Script: `a = [nil]; func c(d) { return d[0] }; c(a)`, RunOutput: nil}, + {Script: `a = [true]; func c(d) { return d[0] }; c(a)`, RunOutput: true}, + {Script: `a = [1]; func c(d) { return d[0] }; c(a)`, RunOutput: int64(1)}, + {Script: `a = [1.1]; func c(d) { return d[0] }; c(a)`, RunOutput: float64(1.1)}, + {Script: `a = ["a"]; func c(d) { return d[0] }; c(a)`, RunOutput: "a"}, + + {Script: `a = {"b": nil}; func c(d) { return d.b }; c(a)`, RunOutput: nil}, + {Script: `a = {"b": true}; func c(d) { return d.b }; c(a)`, RunOutput: true}, + {Script: `a = {"b": 1}; func c(d) { return d.b }; c(a)`, RunOutput: int64(1)}, + {Script: `a = {"b": 1.1}; func c(d) { return d.b }; c(a)`, RunOutput: float64(1.1)}, + {Script: `a = {"b": "a"}; func c(d) { return d.b }; c(a)`, RunOutput: "a"}, + + {Script: `a = func() { return func(c) { return c + "c"} }; a()("a")`, RunOutput: "ac"}, + {Script: `a = func() { return func(c) { return c + "c"} }(); a("a")`, RunOutput: "ac"}, + {Script: `a = func() { return func(c) { return c + "c"} }()("a")`, RunOutput: "ac"}, + {Script: `func() { return func(c) { return c + "c"} }()("a")`, RunOutput: "ac"}, + + {Script: `a = func(b) { return func() { return b + "c"} }; b = a("a"); b()`, RunOutput: "ac"}, + {Script: `a = func(b) { return func() { return b + "c"} }("a"); a()`, RunOutput: "ac"}, + {Script: `a = func(b) { return func() { return b + "c"} }("a")()`, RunOutput: "ac"}, + {Script: `func(b) { return func() { return b + "c"} }("a")()`, RunOutput: "ac"}, + + {Script: `a = func(b) { return func(c) { return b[c] } }; b = a({"x": "x"}); b("x")`, RunOutput: "x"}, + {Script: `a = func(b) { return func(c) { return b[c] } }({"x": "x"}); a("x")`, RunOutput: "x"}, + {Script: `a = func(b) { return func(c) { return b[c] } }({"x": "x"})("x")`, RunOutput: "x"}, + {Script: `func(b) { return func(c) { return b[c] } }({"x": "x"})("x")`, RunOutput: "x"}, + + {Script: `a = func(b) { return func(c) { return b[c] } }; x = {"y": "y"}; b = a(x); x = {"y": "y"}; b("y")`, RunOutput: "y"}, + {Script: `a = func(b) { return func(c) { return b[c] } }; x = {"y": "y"}; b = a(x); x.y = "z"; b("y")`, RunOutput: "z"}, + + {Script: ` func a() { return "a" }; a()`, RunOutput: "a"}, + {Script: `a = func a() { return "a" }; a = func() { return "b" }; a()`, RunOutput: "b"}, + {Script: `a = "a.b"; func a() { return "a" }; a()`, RunOutput: "a"}, + + {Script: `a = func() { b = "b"; return func() { b += "c" } }(); a()`, RunOutput: "bc"}, + {Script: `a = func() { b = "b"; return func() { b += "c"; return b} }(); a()`, RunOutput: "bc"}, + {Script: `a = func(b) { return func(c) { return func(d) { return d + "d" }(c) + "c" }(b) + "b" }("a")`, RunOutput: "adcb"}, + {Script: `a = func(b) { return "b" + func(c) { return "c" + func(d) { return "d" + d }(c) }(b) }("a")`, RunOutput: "bcda"}, + {Script: `a = func(b) { return b + "b" }; a( func(c) { return c + "c" }("a") )`, RunOutput: "acb"}, + + {Script: `a = func(x, y) { return func() { x(y) } }; b = a(func (z) { return z + "z" }, "b"); b()`, RunOutput: "bz"}, + + {Script: `a = make(Time); a.IsZero()`, Types: map[string]interface{}{"Time": time.Time{}}, RunOutput: true}, + + {Script: `a = make(Buffer); n, err = a.WriteString("a"); if err != nil { return err }; n`, Types: map[string]interface{}{"Buffer": bytes.Buffer{}}, RunOutput: 1}, + {Script: `a = make(Buffer); n, err = a.WriteString("a"); if err != nil { return err }; a.String()`, Types: map[string]interface{}{"Buffer": bytes.Buffer{}}, RunOutput: "a"}, + + {Script: `b = {}; c = a(b.c); c`, Input: map[string]interface{}{"a": func(b string) bool { + if b == "" { + return true + } + return false + }}, RunOutput: true}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestPointerFunctions(t *testing.T) { + t.Parallel() + + testFunctionPointer := func(b interface{}) string { + rv := reflect.ValueOf(b) + if !rv.IsValid() { + return "invalid" + } + if rv.Kind() != reflect.Ptr { + return fmt.Sprintf("not ptr: " + rv.Kind().String()) + } + if rv.IsNil() { + return "IsNil" + } + if !rv.Elem().CanInterface() { + return "cannot interface" + } + if rv.Elem().Interface() != int64(1) { + return fmt.Sprintf("not 1: %v", rv.Elem().Interface()) + } + if !rv.Elem().CanSet() { + return "cannot set" + } + slice := reflect.MakeSlice(interfaceSliceType, 0, 1) + value, _ := makeValue(stringType) + value.SetString("b") + slice = reflect.Append(slice, value) + rv.Elem().Set(slice) + return "good" + } + tests := []Test{ + {Script: `b = 1; a(&b)`, Input: map[string]interface{}{"a": testFunctionPointer}, RunOutput: "good", Output: map[string]interface{}{"b": []interface{}{"b"}}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestVariadicFunctions(t *testing.T) { + t.Parallel() + + tests := []Test{ + // params Variadic arg !Variadic + {Script: `func a(b...) { return b }; a()`, RunOutput: []interface{}{}}, + {Script: `func a(b...) { return b }; a(true)`, RunOutput: []interface{}{true}}, + {Script: `func a(b...) { return b }; a(true, true)`, RunOutput: []interface{}{true, true}}, + {Script: `func a(b...) { return b }; a([true])`, RunOutput: []interface{}{[]interface{}{true}}}, + {Script: `func a(b...) { return b }; a([true, true])`, RunOutput: []interface{}{[]interface{}{true, true}}}, + {Script: `func a(b...) { return b }; a([true, true], [true, true])`, RunOutput: []interface{}{[]interface{}{true, true}, []interface{}{true, true}}}, + + // params Variadic arg !Variadic + {Script: `func a(b, c...) { return c }; a()`, RunError: fmt.Errorf("function wants 2 arguments but received 0")}, + {Script: `func a(b, c...) { return c }; a(true)`, RunOutput: []interface{}{}}, + {Script: `func a(b, c...) { return c }; a(true, true)`, RunOutput: []interface{}{true}}, + {Script: `func a(b, c...) { return c }; a(true, true, true)`, RunOutput: []interface{}{true, true}}, + {Script: `func a(b, c...) { return c }; a([true])`, RunOutput: []interface{}{}}, + {Script: `func a(b, c...) { return c }; a([true], [true])`, RunOutput: []interface{}{[]interface{}{true}}}, + {Script: `func a(b, c...) { return c }; a([true], [true], [true])`, RunOutput: []interface{}{[]interface{}{true}, []interface{}{true}}}, + {Script: `func a(b, c...) { return c }; a([true], [true, true], [true, true])`, RunOutput: []interface{}{[]interface{}{true, true}, []interface{}{true, true}}}, + + // params Variadic arg Variadic + {Script: `func a(b...) { return b }; a([true]...)`, RunOutput: []interface{}{true}}, + {Script: `func a(b...) { return b }; a([true, true]...)`, RunOutput: []interface{}{true, true}}, + {Script: `func a(b...) { return b }; a(true, [true]...)`, RunError: fmt.Errorf("function wants 1 arguments but received 2")}, + + // params Variadic arg Variadic + {Script: `func a(b, c...) { return c }; a([true]...)`, RunOutput: []interface{}{}}, + {Script: `func a(b, c...) { return c }; a([true, true]...)`, RunOutput: []interface{}{}}, + {Script: `func a(b, c...) { return c }; a(true, [true]...)`, RunOutput: []interface{}{true}}, + {Script: `func a(b, c...) { return c }; a(true, [true, true]...)`, RunOutput: []interface{}{true, true}}, + + // params !Variadic arg Variadic + {Script: `func a() { return "a" }; a([true]...)`, RunOutput: "a"}, + {Script: `func a() { return "a" }; a(true, [true]...)`, RunOutput: "a"}, + {Script: `func a() { return "a" }; a(true, [true, true]...)`, RunOutput: "a"}, + + // params !Variadic arg Variadic + {Script: `func a(b) { return b }; a(true...)`, RunError: fmt.Errorf("call is variadic but last parameter is of type bool")}, + {Script: `func a(b) { return b }; a([true]...)`, RunOutput: true}, + {Script: `func a(b) { return b }; a(true, false...)`, RunError: fmt.Errorf("function wants 1 arguments but received 2")}, + {Script: `func a(b) { return b }; a(true, [1]...)`, RunError: fmt.Errorf("function wants 1 arguments but received 2")}, + {Script: `func a(b) { return b }; a(true, [1, 2]...)`, RunError: fmt.Errorf("function wants 1 arguments but received 2")}, + {Script: `func a(b) { return b }; a([true, 1]...)`, RunOutput: true}, + {Script: `func a(b) { return b }; a([true, 1, 2]...)`, RunOutput: true}, + + // params !Variadic arg Variadi + {Script: `func a(b, c) { return c }; a(false...)`, RunError: fmt.Errorf("call is variadic but last parameter is of type bool")}, + {Script: `func a(b, c) { return c }; a([1]...)`, RunError: fmt.Errorf("function wants 2 arguments but received 1")}, + {Script: `func a(b, c) { return c }; a(1, true...)`, RunError: fmt.Errorf("call is variadic but last parameter is of type bool")}, + {Script: `func a(b, c) { return c }; a(1, [true]...)`, RunOutput: true}, + {Script: `func a(b, c) { return c }; a([1, true]...)`, RunOutput: true}, + {Script: `func a(b, c) { return c }; a(1, true...)`, RunError: fmt.Errorf("call is variadic but last parameter is of type bool")}, + {Script: `func a(b, c) { return c }; a(1, [true]...)`, RunOutput: true}, + {Script: `func a(b, c) { return c }; a(1, true, false...)`, RunError: fmt.Errorf("function wants 2 arguments but received 3")}, + {Script: `func a(b, c) { return c }; a(1, true, [2]...)`, RunError: fmt.Errorf("function wants 2 arguments but received 3")}, + {Script: `func a(b, c) { return c }; a(1, [true, 2]...)`, RunOutput: true}, + {Script: `func a(b, c) { return c }; a([1, true, 2]...)`, RunOutput: true}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestFunctionsInArraysAndMaps(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `a = [func () { return nil }]; a[0]()`, RunOutput: nil}, + {Script: `a = [func () { return true }]; a[0]()`, RunOutput: true}, + {Script: `a = [func () { return 1 }]; a[0]()`, RunOutput: int64(1)}, + {Script: `a = [func () { return 1.1 }]; a[0]()`, RunOutput: float64(1.1)}, + {Script: `a = [func () { return "a" }]; a[0]()`, RunOutput: "a"}, + + {Script: `a = [func () { return nil }]; b = a[0]; b()`, RunOutput: nil}, + {Script: `a = [func () { return true }]; b = a[0]; b()`, RunOutput: true}, + {Script: `a = [func () { return 1 }]; b = a[0]; b()`, RunOutput: int64(1)}, + {Script: `a = [func () { return 1.1 }]; b = a[0]; b()`, RunOutput: float64(1.1)}, + {Script: `a = [func () { return "a" }]; b = a[0]; b()`, RunOutput: "a"}, + + {Script: `a = [func () { return nil}]; func b(c) { return c() }; b(a[0])`, RunOutput: nil}, + {Script: `a = [func () { return true}]; func b(c) { return c() }; b(a[0])`, RunOutput: true}, + {Script: `a = [func () { return 1}]; func b(c) { return c() }; b(a[0])`, RunOutput: int64(1)}, + {Script: `a = [func () { return 1.1}]; func b(c) { return c() }; b(a[0])`, RunOutput: float64(1.1)}, + {Script: `a = [func () { return "a"}]; func b(c) { return c() }; b(a[0])`, RunOutput: "a"}, + + {Script: `a = {"b": func () { return nil }}; a["b"]()`, RunOutput: nil}, + {Script: `a = {"b": func () { return true }}; a["b"]()`, RunOutput: true}, + {Script: `a = {"b": func () { return 1 }}; a["b"]()`, RunOutput: int64(1)}, + {Script: `a = {"b": func () { return 1.1 }}; a["b"]()`, RunOutput: float64(1.1)}, + {Script: `a = {"b": func () { return "a" }}; a["b"]()`, RunOutput: "a"}, + + {Script: `a = {"b": func () { return nil }}; a.b()`, RunOutput: nil}, + {Script: `a = {"b": func () { return true }}; a.b()`, RunOutput: true}, + {Script: `a = {"b": func () { return 1 }}; a.b()`, RunOutput: int64(1)}, + {Script: `a = {"b": func () { return 1.1 }}; a.b()`, RunOutput: float64(1.1)}, + {Script: `a = {"b": func () { return "a" }}; a.b()`, RunOutput: "a"}, + + {Script: `a = {"b": func () { return nil }}; func c(d) { return d() }; c(a.b)`, RunOutput: nil}, + {Script: `a = {"b": func () { return true }}; func c(d) { return d() }; c(a.b)`, RunOutput: true}, + {Script: `a = {"b": func () { return 1 }}; func c(d) { return d() }; c(a.b)`, RunOutput: int64(1)}, + {Script: `a = {"b": func () { return 1.1 }}; func c(d) { return d() }; c(a.b)`, RunOutput: float64(1.1)}, + {Script: `a = {"b": func () { return "a" }}; func c(d) { return d() }; c(a.b)`, RunOutput: "a"}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestFunctionConversions(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `b = func(c){ return c }; a("x", b)`, Input: map[string]interface{}{"a": func(b string, c func(string) string) string { return c(b) }}, RunOutput: "x"}, + {Script: `b = make(struct1); b.A = func (c, d) { return c == d }; b.A(2, 2)`, Types: map[string]interface{}{"struct1": &struct { + A func(int, int) bool + }{}}, + RunOutput: true}, + {Script: `b = 1; a(&b)`, Input: map[string]interface{}{"a": func(b *int64) { *b = int64(2) }}, Output: map[string]interface{}{"b": int64(2)}}, + {Script: `b = func(){ return true, 1, 2, 3.3, 4.4, "5" }; c, d, e, f, g, h = a(b); c`, Input: map[string]interface{}{"a": func(b func() (bool, int32, int64, float32, float64, string)) (bool, int32, int64, float32, float64, string) { + return b() + }}, RunOutput: true, Output: map[string]interface{}{"c": true, "d": int32(1), "e": int64(2), "f": float32(3.3), "g": float64(4.4), "h": "5"}}, + + // string to byte + {Script: `b = a("yz"); b`, Input: map[string]interface{}{"a": func(b byte) string { return string(b) }}, RunError: fmt.Errorf("function wants argument type uint8 but received type string")}, + {Script: `b = a("x"); b`, Input: map[string]interface{}{"a": func(b byte) string { return string(b) }}, RunOutput: "x"}, + {Script: `b = a(""); b`, Input: map[string]interface{}{"a": func(b byte) string { return string(b) }}, RunOutput: "\x00"}, + // string to rune + {Script: `b = a("yz"); b`, Input: map[string]interface{}{"a": func(b rune) string { return string(b) }}, RunError: fmt.Errorf("function wants argument type int32 but received type string")}, + {Script: `b = a("x"); b`, Input: map[string]interface{}{"a": func(b rune) string { return string(b) }}, RunOutput: "x"}, + {Script: `b = a(""); b`, Input: map[string]interface{}{"a": func(b rune) string { return string(b) }}, RunOutput: "\x00"}, + + // slice inteface unable to convert to int + {Script: `b = [1, 2.2, "3"]; a(b)`, Input: map[string]interface{}{"a": func(b []int) int { return len(b) }}, RunError: fmt.Errorf("function wants argument type []int but received type []interface {}"), Output: map[string]interface{}{"b": []interface{}{int64(1), float64(2.2), "3"}}}, + // slice no sub convertible conversion + {Script: `a(b)`, Input: map[string]interface{}{"a": func(b []int) int { return len(b) }, "b": []int64{1}}, RunOutput: int(1), Output: map[string]interface{}{"b": []int64{1}}}, + // array no sub convertible conversion + {Script: `a(b)`, Input: map[string]interface{}{"a": func(b [2]int) int { return len(b) }, "b": [2]int64{1, 2}}, RunOutput: int(2), Output: map[string]interface{}{"b": [2]int64{1, 2}}}, + // slice no sub to interface conversion + {Script: `a(b)`, Input: map[string]interface{}{"a": func(b []interface{}) int { return len(b) }, "b": []int64{1}}, RunOutput: int(1), Output: map[string]interface{}{"b": []int64{1}}}, + // array no sub to interface conversion + {Script: `a(b)`, Input: map[string]interface{}{"a": func(b [2]interface{}) int { return len(b) }, "b": [2]int64{1, 2}}, RunOutput: int(2), Output: map[string]interface{}{"b": [2]int64{1, 2}}}, + // slice no sub from interface conversion + {Script: `b = [1]; a(b)`, Input: map[string]interface{}{"a": func(b []int) int { return len(b) }}, RunOutput: int(1), Output: map[string]interface{}{"b": []interface{}{int64(1)}}}, + // array no sub from interface conversion + {Script: `a(b)`, Input: map[string]interface{}{"a": func(b [2]int) int { return len(b) }, "b": [2]interface{}{1, 2}}, RunOutput: int(2), Output: map[string]interface{}{"b": [2]interface{}{1, 2}}}, + + // slice sub mismatch + {Script: `a(b)`, Input: map[string]interface{}{"a": func(b []int) int { return len(b) }, "b": [][]int64{{1, 2}}}, RunError: fmt.Errorf("function wants argument type []int but received type [][]int64"), Output: map[string]interface{}{"b": [][]int64{{1, 2}}}}, + // array sub mismatch + {Script: `a(b)`, Input: map[string]interface{}{"a": func(b [2]int) int { return len(b) }, "b": [1][2]int64{{1, 2}}}, RunError: fmt.Errorf("function wants argument type [2]int but received type [1][2]int64"), Output: map[string]interface{}{"b": [1][2]int64{{1, 2}}}}, + + // slice with sub int64 to int conversion + {Script: `a(b)`, Input: map[string]interface{}{"a": func(b [][]int) int { return len(b) }, "b": [][]int64{{1, 2}, {3, 4}}}, RunOutput: int(2), Output: map[string]interface{}{"b": [][]int64{{1, 2}, {3, 4}}}}, + // array with sub int64 to int conversion + {Script: `a(b)`, Input: map[string]interface{}{"a": func(b [][]int) int { return len(b) }, "b": [2][2]int64{{1, 2}, {3, 4}}}, RunOutput: int(2), Output: map[string]interface{}{"b": [2][2]int64{{1, 2}, {3, 4}}}}, + // slice with sub interface to int conversion + {Script: `b = [[1, 2], [3, 4]]; a(b)`, Input: map[string]interface{}{"a": func(b [][]int) int { return len(b) }}, RunOutput: int(2), Output: map[string]interface{}{"b": []interface{}{[]interface{}{int64(1), int64(2)}, []interface{}{int64(3), int64(4)}}}}, + // slice with sub interface to int conversion + {Script: `a(b)`, Input: map[string]interface{}{"a": func(b [][]int) int { return len(b) }, "b": [][]interface{}{{int64(1), int32(2)}, {float64(3.3), float32(4.4)}}}, RunOutput: int(2), Output: map[string]interface{}{"b": [][]interface{}{{int64(1), int32(2)}, {float64(3.3), float32(4.4)}}}}, + // array with sub interface to int conversion + {Script: `a(b)`, Input: map[string]interface{}{"a": func(b [][]int) int { return len(b) }, "b": [2][2]interface{}{{1, 2}, {3, 4}}}, RunOutput: int(2), Output: map[string]interface{}{"b": [2][2]interface{}{{1, 2}, {3, 4}}}}, + // slice with single interface to double interface + {Script: `b = [[1, 2], [3, 4]]; a(b)`, Input: map[string]interface{}{"a": func(b [][]interface{}) int { return len(b) }}, RunOutput: int(2), Output: map[string]interface{}{"b": []interface{}{[]interface{}{int64(1), int64(2)}, []interface{}{int64(3), int64(4)}}}}, + // slice with sub int64 to double interface conversion + {Script: `a(b)`, Input: map[string]interface{}{"a": func(b [][]interface{}) int { return len(b) }, "b": [][]int64{{1, 2}, {3, 4}}}, RunOutput: int(2), Output: map[string]interface{}{"b": [][]int64{{1, 2}, {3, 4}}}}, + // array with sub int64 to double interface conversion + {Script: `a(b)`, Input: map[string]interface{}{"a": func(b [][]interface{}) int { return len(b) }, "b": [2][2]int64{{1, 2}, {3, 4}}}, RunOutput: int(2), Output: map[string]interface{}{"b": [2][2]int64{{1, 2}, {3, 4}}}}, + + // TOFIX: not able to change pointer value + // {Script: `b = 1; c = &b; a(c); *c`, Input: map[string]interface{}{"a": func(b *int64) { *b = int64(2) }}, RunOutput: int64(2), Output: map[string]interface{}{"b": int64(2)}}, + + // map [interface]interface to [interface]interface + {Script: `b = {nil:nil}; c = nil; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[interface{}]interface{}, c interface{}) interface{} { return b[c] }}, RunOutput: nil, Output: map[string]interface{}{"b": map[interface{}]interface{}{nil: nil}, "c": nil}}, + {Script: `b = {true:true}; c = true; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[interface{}]interface{}, c interface{}) interface{} { return b[c] }}, RunOutput: true, Output: map[string]interface{}{"b": map[interface{}]interface{}{true: true}, "c": true}}, + {Script: `b = {1:2}; c = 1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[interface{}]interface{}, c interface{}) interface{} { return b[c] }}, RunOutput: int64(2), Output: map[string]interface{}{"b": map[interface{}]interface{}{int64(1): int64(2)}, "c": int64(1)}}, + {Script: `b = {1.1:2.2}; c = 1.1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[interface{}]interface{}, c interface{}) interface{} { return b[c] }}, RunOutput: float64(2.2), Output: map[string]interface{}{"b": map[interface{}]interface{}{float64(1.1): float64(2.2)}, "c": float64(1.1)}}, + {Script: `b = {"a":"b"}; c = "a"; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[interface{}]interface{}, c interface{}) interface{} { return b[c] }}, RunOutput: "b", Output: map[string]interface{}{"b": map[interface{}]interface{}{"a": "b"}, "c": "a"}}, + + // map [interface]interface to [bool]interface + {Script: `b = {"a":"b"}; c = true; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[bool]interface{}, c bool) interface{} { return b[c] }}, RunError: fmt.Errorf("function wants argument type map[bool]interface {} but received type map[interface {}]interface {}"), Output: map[string]interface{}{"b": map[interface{}]interface{}{"a": "b"}, "c": true}}, + {Script: `b = {"a":"b"}; c = "a"; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[bool]interface{}, c bool) interface{} { return b[c] }}, RunError: fmt.Errorf("function wants argument type map[bool]interface {} but received type map[interface {}]interface {}"), Output: map[string]interface{}{"b": map[interface{}]interface{}{"a": "b"}, "c": "a"}}, + {Script: `b = {true:"b"}; c = "a"; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[bool]interface{}, c bool) interface{} { return b[c] }}, RunError: fmt.Errorf("function wants argument type bool but received type string"), Output: map[string]interface{}{"b": map[interface{}]interface{}{true: "b"}, "c": "a"}}, + {Script: `b = {true:nil}; c = true; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[bool]interface{}, c bool) interface{} { return b[c] }}, RunOutput: nil, Output: map[string]interface{}{"b": map[interface{}]interface{}{true: nil}, "c": true}}, + {Script: `b = {true:true}; c = true; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[bool]interface{}, c bool) interface{} { return b[c] }}, RunOutput: true, Output: map[string]interface{}{"b": map[interface{}]interface{}{true: true}, "c": true}}, + {Script: `b = {true:2}; c = true; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[bool]interface{}, c bool) interface{} { return b[c] }}, RunOutput: int64(2), Output: map[string]interface{}{"b": map[interface{}]interface{}{true: int64(2)}, "c": true}}, + {Script: `b = {true:2.2}; c = true; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[bool]interface{}, c bool) interface{} { return b[c] }}, RunOutput: float64(2.2), Output: map[string]interface{}{"b": map[interface{}]interface{}{true: float64(2.2)}, "c": true}}, + {Script: `b = {true:"b"}; c = true; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[bool]interface{}, c bool) interface{} { return b[c] }}, RunOutput: "b", Output: map[string]interface{}{"b": map[interface{}]interface{}{true: "b"}, "c": true}}, + + // map [interface]interface to [int32]interface + {Script: `b = {1:nil}; c = 1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[int32]interface{}, c int32) interface{} { return b[c] }}, RunOutput: nil, Output: map[string]interface{}{"b": map[interface{}]interface{}{int64(1): nil}, "c": int64(1)}}, + {Script: `b = {1:true}; c = 1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[int32]interface{}, c int32) interface{} { return b[c] }}, RunOutput: true, Output: map[string]interface{}{"b": map[interface{}]interface{}{int64(1): true}, "c": int64(1)}}, + {Script: `b = {1:2}; c = 1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[int32]interface{}, c int32) interface{} { return b[c] }}, RunOutput: int64(2), Output: map[string]interface{}{"b": map[interface{}]interface{}{int64(1): int64(2)}, "c": int64(1)}}, + {Script: `b = {1:2.2}; c = 1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[int32]interface{}, c int32) interface{} { return b[c] }}, RunOutput: float64(2.2), Output: map[string]interface{}{"b": map[interface{}]interface{}{int64(1): float64(2.2)}, "c": int64(1)}}, + {Script: `b = {1:"b"}; c = 1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[int32]interface{}, c int32) interface{} { return b[c] }}, RunOutput: "b", Output: map[string]interface{}{"b": map[interface{}]interface{}{int64(1): "b"}, "c": int64(1)}}, + + // map [interface]interface to [int64]interface + {Script: `b = {1:nil}; c = 1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[int64]interface{}, c int64) interface{} { return b[c] }}, RunOutput: nil, Output: map[string]interface{}{"b": map[interface{}]interface{}{int64(1): nil}, "c": int64(1)}}, + {Script: `b = {1:true}; c = 1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[int64]interface{}, c int64) interface{} { return b[c] }}, RunOutput: true, Output: map[string]interface{}{"b": map[interface{}]interface{}{int64(1): true}, "c": int64(1)}}, + {Script: `b = {1:2}; c = 1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[int64]interface{}, c int64) interface{} { return b[c] }}, RunOutput: int64(2), Output: map[string]interface{}{"b": map[interface{}]interface{}{int64(1): int64(2)}, "c": int64(1)}}, + {Script: `b = {1:2.2}; c = 1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[int64]interface{}, c int64) interface{} { return b[c] }}, RunOutput: float64(2.2), Output: map[string]interface{}{"b": map[interface{}]interface{}{int64(1): float64(2.2)}, "c": int64(1)}}, + {Script: `b = {1:"b"}; c = 1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[int64]interface{}, c int64) interface{} { return b[c] }}, RunOutput: "b", Output: map[string]interface{}{"b": map[interface{}]interface{}{int64(1): "b"}, "c": int64(1)}}, + + // map [interface]interface to [float32]interface + {Script: `b = {1.1:nil}; c = 1.1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[float32]interface{}, c float32) interface{} { return b[c] }}, RunOutput: nil, Output: map[string]interface{}{"b": map[interface{}]interface{}{float64(1.1): nil}, "c": float64(1.1)}}, + {Script: `b = {1.1:true}; c = 1.1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[float32]interface{}, c float32) interface{} { return b[c] }}, RunOutput: true, Output: map[string]interface{}{"b": map[interface{}]interface{}{float64(1.1): true}, "c": float64(1.1)}}, + {Script: `b = {1.1:2}; c = 1.1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[float32]interface{}, c float32) interface{} { return b[c] }}, RunOutput: int64(2), Output: map[string]interface{}{"b": map[interface{}]interface{}{float64(1.1): int64(2)}, "c": float64(1.1)}}, + {Script: `b = {1.1:2.2}; c = 1.1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[float32]interface{}, c float32) interface{} { return b[c] }}, RunOutput: float64(2.2), Output: map[string]interface{}{"b": map[interface{}]interface{}{float64(1.1): float64(2.2)}, "c": float64(1.1)}}, + {Script: `b = {1.1:"b"}; c = 1.1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[float32]interface{}, c float32) interface{} { return b[c] }}, RunOutput: "b", Output: map[string]interface{}{"b": map[interface{}]interface{}{float64(1.1): "b"}, "c": float64(1.1)}}, + + // map [interface]interface to [float64]interface + {Script: `b = {1.1:nil}; c = 1.1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[float64]interface{}, c float64) interface{} { return b[c] }}, RunOutput: nil, Output: map[string]interface{}{"b": map[interface{}]interface{}{float64(1.1): nil}, "c": float64(1.1)}}, + {Script: `b = {1.1:true}; c = 1.1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[float64]interface{}, c float64) interface{} { return b[c] }}, RunOutput: true, Output: map[string]interface{}{"b": map[interface{}]interface{}{float64(1.1): true}, "c": float64(1.1)}}, + {Script: `b = {1.1:2}; c = 1.1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[float64]interface{}, c float64) interface{} { return b[c] }}, RunOutput: int64(2), Output: map[string]interface{}{"b": map[interface{}]interface{}{float64(1.1): int64(2)}, "c": float64(1.1)}}, + {Script: `b = {1.1:2.2}; c = 1.1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[float64]interface{}, c float64) interface{} { return b[c] }}, RunOutput: float64(2.2), Output: map[string]interface{}{"b": map[interface{}]interface{}{float64(1.1): float64(2.2)}, "c": float64(1.1)}}, + {Script: `b = {1.1:"b"}; c = 1.1; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[float64]interface{}, c float64) interface{} { return b[c] }}, RunOutput: "b", Output: map[string]interface{}{"b": map[interface{}]interface{}{float64(1.1): "b"}, "c": float64(1.1)}}, + + // map [interface]interface to [string]interface + {Script: `b = {"a":nil}; c = "a"; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[string]interface{}, c string) interface{} { return b[c] }}, RunOutput: nil, Output: map[string]interface{}{"b": map[interface{}]interface{}{"a": nil}, "c": "a"}}, + {Script: `b = {"a":true}; c = "a"; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[string]interface{}, c string) interface{} { return b[c] }}, RunOutput: true, Output: map[string]interface{}{"b": map[interface{}]interface{}{"a": true}, "c": "a"}}, + {Script: `b = {"a":2}; c = "a"; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[string]interface{}, c string) interface{} { return b[c] }}, RunOutput: int64(2), Output: map[string]interface{}{"b": map[interface{}]interface{}{"a": int64(2)}, "c": "a"}}, + {Script: `b = {"a":2.2}; c = "a"; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[string]interface{}, c string) interface{} { return b[c] }}, RunOutput: float64(2.2), Output: map[string]interface{}{"b": map[interface{}]interface{}{"a": float64(2.2)}, "c": "a"}}, + {Script: `b = {"a":"b"}; c = "a"; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[string]interface{}, c string) interface{} { return b[c] }}, RunOutput: "b", Output: map[string]interface{}{"b": map[interface{}]interface{}{"a": "b"}, "c": "a"}}, + + // map [interface]interface to [string]X + {Script: `b = {"a":"b"}; c = "a"; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[string]bool, c string) bool { return b[c] }}, RunError: fmt.Errorf("function wants argument type map[string]bool but received type map[interface {}]interface {}"), Output: map[string]interface{}{"b": map[interface{}]interface{}{"a": "b"}, "c": "a"}}, + {Script: `b = {"a":true}; c = "a"; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[string]bool, c string) bool { return b[c] }}, RunOutput: true, Output: map[string]interface{}{"b": map[interface{}]interface{}{"a": true}, "c": "a"}}, + {Script: `b = {"a":1}; c = "a"; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[string]int32, c string) int32 { return b[c] }}, RunOutput: int32(1), Output: map[string]interface{}{"b": map[interface{}]interface{}{"a": int64(1)}, "c": "a"}}, + {Script: `b = {"a":1}; c = "a"; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[string]int64, c string) int64 { return b[c] }}, RunOutput: int64(1), Output: map[string]interface{}{"b": map[interface{}]interface{}{"a": int64(1)}, "c": "a"}}, + {Script: `b = {"a":1.1}; c = "a"; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[string]float32, c string) float32 { return b[c] }}, RunOutput: float32(1.1), Output: map[string]interface{}{"b": map[interface{}]interface{}{"a": float64(1.1)}, "c": "a"}}, + {Script: `b = {"a":1.1}; c = "a"; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[string]float64, c string) float64 { return b[c] }}, RunOutput: float64(1.1), Output: map[string]interface{}{"b": map[interface{}]interface{}{"a": float64(1.1)}, "c": "a"}}, + {Script: `b = {"a":"b"}; c = "a"; d = a(b, c)`, Input: map[string]interface{}{"a": func(b map[string]string, c string) string { return b[c] }}, RunOutput: "b", Output: map[string]interface{}{"b": map[interface{}]interface{}{"a": "b"}, "c": "a"}}, + } + runTests(t, tests, nil, &Options{Debug: true}) + + tests = []Test{ + {Script: `c = a(b)`, + Input: map[string]interface{}{"a": func(b func() bool) bool { + return b() + }, "b": func(c func(bool)) { c(true) }}, RunError: fmt.Errorf("function wants argument type func() bool but received type func(func(bool))")}, + {Script: `b = func(){ return 1++ }; c = a(b)`, + Input: map[string]interface{}{"a": func(b func() bool) bool { + return b() + }}, RunError: fmt.Errorf("invalid operation")}, + {Script: `b = func(){ return true }; c = a(b)`, + Input: map[string]interface{}{"a": func(b func() string) string { + return b() + }}, RunError: fmt.Errorf("function wants return type string but received type bool")}, + {Script: `b = func(){ return true }; c = a(b)`, + Input: map[string]interface{}{"a": func(b func() (bool, string)) (bool, string) { + return b() + }}, RunError: fmt.Errorf("function wants 2 return values but received bool")}, + {Script: `b = func(){ return true, 1 }; c = a(b)`, + Input: map[string]interface{}{"a": func(b func() (bool, int64, string)) (bool, int64, string) { + return b() + }}, RunError: fmt.Errorf("function wants 3 return values but received 2 values")}, + {Script: `b = func(){ return "1", true }; c = a(b)`, + Input: map[string]interface{}{"a": func(b func() (bool, string)) (bool, string) { + return b() + }}, RunError: fmt.Errorf("function wants return type bool but received type string")}, + } + runTests(t, tests, nil, &Options{Debug: false}) +} + +func TestVariadicFunctionConversions(t *testing.T) { + t.Parallel() + + testSumFunc := func(nums ...int64) int64 { + var total int64 + for _, num := range nums { + total += num + } + return total + } + tests := []Test{ + // params Variadic arg !Variadic + {Script: `a(true)`, Input: map[string]interface{}{"a": func(b ...interface{}) []interface{} { return b }}, RunOutput: []interface{}{true}}, + + {Script: `a()`, Input: map[string]interface{}{"a": testSumFunc}, RunOutput: int64(0)}, + {Script: `a(1)`, Input: map[string]interface{}{"a": testSumFunc}, RunOutput: int64(1)}, + {Script: `a(1, 2)`, Input: map[string]interface{}{"a": testSumFunc}, RunOutput: int64(3)}, + {Script: `a(1, 2, 3)`, Input: map[string]interface{}{"a": testSumFunc}, RunOutput: int64(6)}, + + // TODO: add more tests + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestLen(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `len(1++)`, RunError: fmt.Errorf("invalid operation")}, + {Script: `len(true)`, RunError: fmt.Errorf("type bool does not support len operation")}, + + {Script: `a = ""; len(a)`, RunOutput: int64(0)}, + {Script: `a = "test"; len(a)`, RunOutput: int64(4)}, + {Script: `a = []; len(a)`, RunOutput: int64(0)}, + {Script: `a = [nil]; len(a)`, RunOutput: int64(1)}, + {Script: `a = [true]; len(a)`, RunOutput: int64(1)}, + {Script: `a = ["test"]; len(a)`, RunOutput: int64(1)}, + {Script: `a = [1]; len(a)`, RunOutput: int64(1)}, + {Script: `a = [1.1]; len(a)`, RunOutput: int64(1)}, + + {Script: `a = [[]]; len(a)`, RunOutput: int64(1)}, + {Script: `a = [[nil]]; len(a)`, RunOutput: int64(1)}, + {Script: `a = [[true]]; len(a)`, RunOutput: int64(1)}, + {Script: `a = [["test"]]; len(a)`, RunOutput: int64(1)}, + {Script: `a = [[1]]; len(a)`, RunOutput: int64(1)}, + {Script: `a = [[1.1]]; len(a)`, RunOutput: int64(1)}, + + {Script: `a = [[]]; len(a[0])`, RunOutput: int64(0)}, + {Script: `a = [[nil]]; len(a[0])`, RunOutput: int64(1)}, + {Script: `a = [[true]]; len(a[0])`, RunOutput: int64(1)}, + {Script: `a = [["test"]]; len(a[0])`, RunOutput: int64(1)}, + {Script: `a = [[1]]; len(a[0])`, RunOutput: int64(1)}, + {Script: `a = [[1.1]]; len(a[0])`, RunOutput: int64(1)}, + + {Script: `len(a)`, Input: map[string]interface{}{"a": "a"}, RunOutput: int64(1), Output: map[string]interface{}{"a": "a"}}, + {Script: `len(a)`, Input: map[string]interface{}{"a": map[string]interface{}{}}, RunOutput: int64(0), Output: map[string]interface{}{"a": map[string]interface{}{}}}, + {Script: `len(a)`, Input: map[string]interface{}{"a": map[string]interface{}{"test": "test"}}, RunOutput: int64(1), Output: map[string]interface{}{"a": map[string]interface{}{"test": "test"}}}, + {Script: `len(a["test"])`, Input: map[string]interface{}{"a": map[string]interface{}{"test": "test"}}, RunOutput: int64(4), Output: map[string]interface{}{"a": map[string]interface{}{"test": "test"}}}, + + {Script: `len(a)`, Input: map[string]interface{}{"a": []interface{}{}}, RunOutput: int64(0), Output: map[string]interface{}{"a": []interface{}{}}}, + {Script: `len(a)`, Input: map[string]interface{}{"a": []interface{}{nil}}, RunOutput: int64(1), Output: map[string]interface{}{"a": []interface{}{nil}}}, + {Script: `len(a)`, Input: map[string]interface{}{"a": []interface{}{true}}, RunOutput: int64(1), Output: map[string]interface{}{"a": []interface{}{true}}}, + {Script: `len(a)`, Input: map[string]interface{}{"a": []interface{}{int32(1)}}, RunOutput: int64(1), Output: map[string]interface{}{"a": []interface{}{int32(1)}}}, + {Script: `len(a)`, Input: map[string]interface{}{"a": []interface{}{int64(1)}}, RunOutput: int64(1), Output: map[string]interface{}{"a": []interface{}{int64(1)}}}, + {Script: `len(a)`, Input: map[string]interface{}{"a": []interface{}{float32(1.1)}}, RunOutput: int64(1), Output: map[string]interface{}{"a": []interface{}{float32(1.1)}}}, + {Script: `len(a)`, Input: map[string]interface{}{"a": []interface{}{float64(1.1)}}, RunOutput: int64(1), Output: map[string]interface{}{"a": []interface{}{float64(1.1)}}}, + {Script: `len(a)`, Input: map[string]interface{}{"a": []interface{}{"a"}}, RunOutput: int64(1), Output: map[string]interface{}{"a": []interface{}{"a"}}}, + + {Script: `len(a[0])`, Input: map[string]interface{}{"a": []interface{}{"test"}}, RunOutput: int64(4), Output: map[string]interface{}{"a": []interface{}{"test"}}}, + + {Script: `len(a)`, Input: map[string]interface{}{"a": [][]interface{}{}}, RunOutput: int64(0), Output: map[string]interface{}{"a": [][]interface{}{}}}, + {Script: `len(a)`, Input: map[string]interface{}{"a": [][]interface{}{nil}}, RunOutput: int64(1), Output: map[string]interface{}{"a": [][]interface{}{nil}}}, + {Script: `len(a)`, Input: map[string]interface{}{"a": [][]interface{}{{nil}}}, RunOutput: int64(1), Output: map[string]interface{}{"a": [][]interface{}{{nil}}}}, + {Script: `len(a)`, Input: map[string]interface{}{"a": [][]interface{}{{true}}}, RunOutput: int64(1), Output: map[string]interface{}{"a": [][]interface{}{{true}}}}, + {Script: `len(a)`, Input: map[string]interface{}{"a": [][]interface{}{{int32(1)}}}, RunOutput: int64(1), Output: map[string]interface{}{"a": [][]interface{}{{int32(1)}}}}, + {Script: `len(a)`, Input: map[string]interface{}{"a": [][]interface{}{{int64(1)}}}, RunOutput: int64(1), Output: map[string]interface{}{"a": [][]interface{}{{int64(1)}}}}, + {Script: `len(a)`, Input: map[string]interface{}{"a": [][]interface{}{{float32(1.1)}}}, RunOutput: int64(1), Output: map[string]interface{}{"a": [][]interface{}{{float32(1.1)}}}}, + {Script: `len(a)`, Input: map[string]interface{}{"a": [][]interface{}{{float64(1.1)}}}, RunOutput: int64(1), Output: map[string]interface{}{"a": [][]interface{}{{float64(1.1)}}}}, + {Script: `len(a)`, Input: map[string]interface{}{"a": [][]interface{}{{"a"}}}, RunOutput: int64(1), Output: map[string]interface{}{"a": [][]interface{}{{"a"}}}}, + + {Script: `len(a[0])`, Input: map[string]interface{}{"a": [][]interface{}{nil}}, RunOutput: int64(0), Output: map[string]interface{}{"a": [][]interface{}{nil}}}, + {Script: `len(a[0])`, Input: map[string]interface{}{"a": [][]interface{}{{nil}}}, RunOutput: int64(1), Output: map[string]interface{}{"a": [][]interface{}{{nil}}}}, + {Script: `len(a[0])`, Input: map[string]interface{}{"a": [][]interface{}{{true}}}, RunOutput: int64(1), Output: map[string]interface{}{"a": [][]interface{}{{true}}}}, + {Script: `len(a[0])`, Input: map[string]interface{}{"a": [][]interface{}{{int32(1)}}}, RunOutput: int64(1), Output: map[string]interface{}{"a": [][]interface{}{{int32(1)}}}}, + {Script: `len(a[0])`, Input: map[string]interface{}{"a": [][]interface{}{{int64(1)}}}, RunOutput: int64(1), Output: map[string]interface{}{"a": [][]interface{}{{int64(1)}}}}, + {Script: `len(a[0])`, Input: map[string]interface{}{"a": [][]interface{}{{float32(1.1)}}}, RunOutput: int64(1), Output: map[string]interface{}{"a": [][]interface{}{{float32(1.1)}}}}, + {Script: `len(a[0])`, Input: map[string]interface{}{"a": [][]interface{}{{float64(1.1)}}}, RunOutput: int64(1), Output: map[string]interface{}{"a": [][]interface{}{{float64(1.1)}}}}, + {Script: `len(a[0])`, Input: map[string]interface{}{"a": [][]interface{}{{"a"}}}, RunOutput: int64(1), Output: map[string]interface{}{"a": [][]interface{}{{"a"}}}}, + + {Script: `len(a[0][0])`, Input: map[string]interface{}{"a": [][]interface{}{{"test"}}}, RunOutput: int64(4), Output: map[string]interface{}{"a": [][]interface{}{{"test"}}}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestCallFunctionWithVararg(t *testing.T) { + t.Parallel() + + e := env.NewEnv() + err := e.Define("X", func(args ...string) []string { + return args + }) + if err != nil { + t.Errorf("Define error: %v", err) + } + want := []string{"foo", "bar", "baz"} + err = e.Define("a", want) + if err != nil { + t.Errorf("Define error: %v", err) + } + got, err := Execute(e, nil, "X(a...)") + if err != nil { + t.Errorf("execute error - received %#v - expected: %#v", err, nil) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("execute error - received %#v - expected: %#v", got, want) + } +} + +func TestGoFunctionConcurrency(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: ` +waitGroup.Add(5); +a = []; b = []; c = []; d = []; e = [] +fa = func() { for i = 0; i < 100; i++ { a += 1 }; waitGroup.Done() } +fb = func() { for i = 0; i < 100; i++ { b += 2 }; waitGroup.Done() } +fc = func() { for i = 0; i < 100; i++ { c += 3 }; waitGroup.Done() } +fd = func() { for i = 0; i < 100; i++ { d += 4 }; waitGroup.Done() } +fe = func() { for i = 0; i < 100; i++ { e += 5 }; waitGroup.Done() } +go fa(); go fb(); go fc(); go fd(); go fe() +waitGroup.Wait()`, + Input: map[string]interface{}{"waitGroup": &sync.WaitGroup{}}, + Output: map[string]interface{}{ + "a": []interface{}{int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1)}, + "b": []interface{}{int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2)}, + "c": []interface{}{int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3)}, + "d": []interface{}{int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4)}, + "e": []interface{}{int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5)}, + }}, + {Script: ` +waitGroup.Add(5); +x = [1, 2, 3, 4, 5] +a = []; b = []; c = []; d = []; e = [] +fa = func() { for i = 0; i < 100; i++ { a += x[0] }; waitGroup.Done() } +fb = func() { for i = 0; i < 100; i++ { b += x[1] }; waitGroup.Done() } +fc = func() { for i = 0; i < 100; i++ { c += x[2] }; waitGroup.Done() } +fd = func() { for i = 0; i < 100; i++ { d += x[3] }; waitGroup.Done() } +fe = func() { for i = 0; i < 100; i++ { e += x[4] }; waitGroup.Done() } +go fa(); go fb(); go fc(); go fd(); go fe() +waitGroup.Wait()`, + Input: map[string]interface{}{"waitGroup": &sync.WaitGroup{}}, + Output: map[string]interface{}{ + "a": []interface{}{int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1)}, + "b": []interface{}{int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2)}, + "c": []interface{}{int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3)}, + "d": []interface{}{int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4)}, + "e": []interface{}{int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5)}, + }}, + {Script: ` +waitGroup.Add(5); +x = func(y) { return y } +a = []; b = []; c = []; d = []; e = [] +fa = func() { for i = 0; i < 100; i++ { a += x(1) }; waitGroup.Done() } +fb = func() { for i = 0; i < 100; i++ { b += x(2) }; waitGroup.Done() } +fc = func() { for i = 0; i < 100; i++ { c += x(3) }; waitGroup.Done() } +fd = func() { for i = 0; i < 100; i++ { d += x(4) }; waitGroup.Done() } +fe = func() { for i = 0; i < 100; i++ { e += x(5) }; waitGroup.Done() } +go fa(); go fb(); go fc(); go fd(); go fe() +waitGroup.Wait()`, + Input: map[string]interface{}{"waitGroup": &sync.WaitGroup{}}, + Output: map[string]interface{}{ + "a": []interface{}{int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1), int64(1)}, + "b": []interface{}{int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2), int64(2)}, + "c": []interface{}{int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3), int64(3)}, + "d": []interface{}{int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4), int64(4)}, + "e": []interface{}{int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5), int64(5)}, + }}, + } + + runTests(t, tests, nil, &Options{Debug: true}) +} diff --git a/src/tool/run/vm/vmLetExpr.go b/src/tool/run/vm/vmLetExpr.go new file mode 100644 index 0000000..865e2ee --- /dev/null +++ b/src/tool/run/vm/vmLetExpr.go @@ -0,0 +1,382 @@ +package vm + +import ( + "reflect" + + "github.com/surdeus/goblin/src/tool/run/ast" + "github.com/surdeus/goblin/src/tool/run/env" +) + +func (runInfo *runInfoStruct) invokeLetExpr() { + switch expr := runInfo.expr.(type) { + + // IdentExpr + case *ast.IdentExpr: + if runInfo.env.SetValue(expr.Lit, runInfo.rv) != nil { + runInfo.err = nil + runInfo.env.DefineValue(expr.Lit, runInfo.rv) + } + + // MemberExpr + case *ast.MemberExpr: + value := runInfo.rv + + runInfo.expr = expr.Expr + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + + if runInfo.rv.Kind() == reflect.Interface && !runInfo.rv.IsNil() { + runInfo.rv = runInfo.rv.Elem() + } + + if env, ok := runInfo.rv.Interface().(*env.Env); ok { + runInfo.err = env.SetValue(expr.Name, value) + if runInfo.err != nil { + runInfo.err = newError(expr, runInfo.err) + runInfo.rv = nilValue + } + return + } + + if runInfo.rv.Kind() == reflect.Ptr { + runInfo.rv = runInfo.rv.Elem() + } + + switch runInfo.rv.Kind() { + + // Struct + case reflect.Struct: + field, found := runInfo.rv.Type().FieldByName(expr.Name) + if !found { + runInfo.err = newStringError(expr, "no member named '"+expr.Name+"' for struct") + runInfo.rv = nilValue + return + } + runInfo.rv = runInfo.rv.FieldByIndex(field.Index) + // From reflect CanSet: + // A Value can be changed only if it is addressable and was not obtained by the use of unexported struct fields. + // Often a struct has to be passed as a pointer to be set + if !runInfo.rv.CanSet() { + runInfo.err = newStringError(expr, "struct member '"+expr.Name+"' cannot be assigned") + runInfo.rv = nilValue + return + } + + value, runInfo.err = convertReflectValueToType(value, runInfo.rv.Type()) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "type "+value.Type().String()+" cannot be assigned to type "+runInfo.rv.Type().String()+" for struct") + runInfo.rv = nilValue + return + } + + runInfo.rv.Set(value) + return + + // Map + case reflect.Map: + value, runInfo.err = convertReflectValueToType(value, runInfo.rv.Type().Elem()) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "type "+value.Type().String()+" cannot be assigned to type "+runInfo.rv.Type().Elem().String()+" for map") + runInfo.rv = nilValue + return + } + if runInfo.rv.IsNil() { + // make new map + item := reflect.MakeMap(runInfo.rv.Type()) + item.SetMapIndex(reflect.ValueOf(expr.Name), value) + // assign new map + runInfo.rv = item + runInfo.expr = expr.Expr + runInfo.invokeLetExpr() + runInfo.rv = item.MapIndex(reflect.ValueOf(expr.Name)) + return + } + runInfo.rv.SetMapIndex(reflect.ValueOf(expr.Name), value) + + default: + runInfo.err = newStringError(expr, "type "+runInfo.rv.Kind().String()+" does not support member operation") + runInfo.rv = nilValue + } + + // ItemExpr + case *ast.ItemExpr: + value := runInfo.rv + + runInfo.expr = expr.Item + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + item := runInfo.rv + + runInfo.expr = expr.Index + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + + if item.Kind() == reflect.Interface && !item.IsNil() { + item = item.Elem() + } + + switch item.Kind() { + + // Slice && Array + case reflect.Slice, reflect.Array: + var index int + index, runInfo.err = tryToInt(runInfo.rv) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "index must be a number") + runInfo.rv = nilValue + return + } + + if index == item.Len() { + // try to do automatic append + value, runInfo.err = convertReflectValueToType(value, item.Type().Elem()) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "type "+value.Type().String()+" cannot be assigned to type "+item.Type().Elem().String()+" for slice index") + runInfo.rv = nilValue + return + } + item = reflect.Append(item, value) + runInfo.rv = item + runInfo.expr = expr.Item + runInfo.invokeLetExpr() + runInfo.rv = item.Index(index) + return + } + + if index < 0 || index >= item.Len() { + runInfo.err = newStringError(expr, "index out of range") + runInfo.rv = nilValue + return + } + item = item.Index(index) + if !item.CanSet() { + runInfo.err = newStringError(expr, "index cannot be assigned") + runInfo.rv = nilValue + return + } + + value, runInfo.err = convertReflectValueToType(value, item.Type()) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "type "+value.Type().String()+" cannot be assigned to type "+item.Type().String()+" for slice index") + runInfo.rv = nilValue + return + } + + item.Set(value) + runInfo.rv = item + + // Map + case reflect.Map: + runInfo.rv, runInfo.err = convertReflectValueToType(runInfo.rv, item.Type().Key()) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "index type "+runInfo.rv.Type().String()+" cannot be used for map index type "+item.Type().Key().String()) + runInfo.rv = nilValue + return + } + + value, runInfo.err = convertReflectValueToType(value, item.Type().Elem()) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "type "+value.Type().String()+" cannot be assigned to type "+item.Type().Elem().String()+" for map") + runInfo.rv = nilValue + return + } + + if item.IsNil() { + // make new map + item = reflect.MakeMap(item.Type()) + item.SetMapIndex(runInfo.rv, value) + mapIndex := runInfo.rv + // assign new map + runInfo.rv = item + runInfo.expr = expr.Item + runInfo.invokeLetExpr() + runInfo.rv = item.MapIndex(mapIndex) + return + } + item.SetMapIndex(runInfo.rv, value) + + // String + case reflect.String: + var index int + index, runInfo.err = tryToInt(runInfo.rv) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "index must be a number") + runInfo.rv = nilValue + return + } + + value, runInfo.err = convertReflectValueToType(value, item.Type()) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "type "+value.Type().String()+" cannot be assigned to type "+item.Type().String()) + runInfo.rv = nilValue + return + } + + if index == item.Len() { + // automatic append + if item.CanSet() { + item.SetString(item.String() + value.String()) + return + } + + runInfo.rv = reflect.ValueOf(item.String() + value.String()) + runInfo.expr = expr.Item + runInfo.invokeLetExpr() + return + } + + if index < 0 || index >= item.Len() { + runInfo.err = newStringError(expr, "index out of range") + runInfo.rv = nilValue + return + } + + if item.CanSet() { + item.SetString(item.Slice(0, index).String() + value.String() + item.Slice(index+1, item.Len()).String()) + runInfo.rv = item + return + } + + runInfo.rv = reflect.ValueOf(item.Slice(0, index).String() + value.String() + item.Slice(index+1, item.Len()).String()) + runInfo.expr = expr.Item + runInfo.invokeLetExpr() + + default: + runInfo.err = newStringError(expr, "type "+item.Kind().String()+" does not support index operation") + runInfo.rv = nilValue + } + + // SliceExpr + case *ast.SliceExpr: + value := runInfo.rv + + runInfo.expr = expr.Item + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + item := runInfo.rv + + if item.Kind() == reflect.Interface && !item.IsNil() { + item = item.Elem() + } + + switch item.Kind() { + + // Slice && Array + case reflect.Slice, reflect.Array: + var beginIndex int + endIndex := item.Len() + + if expr.Begin != nil { + runInfo.expr = expr.Begin + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + beginIndex, runInfo.err = tryToInt(runInfo.rv) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "index must be a number") + runInfo.rv = nilValue + return + } + // (0 <= low) <= high <= len(a) + if beginIndex < 0 { + runInfo.err = newStringError(expr, "index out of range") + runInfo.rv = nilValue + return + } + } + + if expr.End != nil { + runInfo.expr = expr.End + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + endIndex, runInfo.err = tryToInt(runInfo.rv) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "index must be a number") + runInfo.rv = nilValue + return + } + // 0 <= low <= (high <= len(a)) + if endIndex > item.Len() { + runInfo.err = newStringError(expr, "index out of range") + runInfo.rv = nilValue + return + } + } + + // 0 <= (low <= high) <= len(a) + if beginIndex > endIndex { + runInfo.err = newStringError(expr, "index out of range") + runInfo.rv = nilValue + return + } + + sliceCap := item.Cap() + if expr.Cap != nil { + runInfo.expr = expr.Cap + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + sliceCap, runInfo.err = tryToInt(runInfo.rv) + if runInfo.err != nil { + runInfo.err = newStringError(expr, "cap must be a number") + runInfo.rv = nilValue + return + } + // 0 <= low <= (high <= max <= cap(a)) + if sliceCap < endIndex || sliceCap > item.Cap() { + runInfo.err = newStringError(expr, "cap out of range") + runInfo.rv = nilValue + return + } + } + + item = item.Slice3(beginIndex, endIndex, sliceCap) + + if !item.CanSet() { + runInfo.err = newStringError(expr, "slice cannot be assigned") + runInfo.rv = nilValue + return + } + item.Set(value) + + // String + case reflect.String: + runInfo.err = newStringError(expr, "type string does not support slice operation for assignment") + runInfo.rv = nilValue + + default: + runInfo.err = newStringError(expr, "type "+item.Kind().String()+" does not support slice operation") + runInfo.rv = nilValue + } + + // DerefExpr + case *ast.DerefExpr: + value := runInfo.rv + + runInfo.expr = expr.Expr + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + + runInfo.rv.Elem().Set(value) + runInfo.rv = value + + default: + runInfo.err = newStringError(expr, "invalid operation") + runInfo.rv = nilValue + } + +} diff --git a/src/tool/run/vm/vmOperator.go b/src/tool/run/vm/vmOperator.go new file mode 100644 index 0000000..527252a --- /dev/null +++ b/src/tool/run/vm/vmOperator.go @@ -0,0 +1,228 @@ +package vm + +import ( + "reflect" + "strings" + + "github.com/surdeus/goblin/src/tool/run/ast" +) + +// invokeOperator evaluates one Operator. +func (runInfo *runInfoStruct) invokeOperator() { + switch operator := runInfo.operator.(type) { + + // BinaryOperator + case *ast.BinaryOperator: + runInfo.expr = operator.LHS + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + if runInfo.rv.Kind() == reflect.Interface && !runInfo.rv.IsNil() { + runInfo.rv = runInfo.rv.Elem() + } + + switch operator.Operator { + case "||": + if toBool(runInfo.rv) { + runInfo.rv = trueValue + return + } + case "&&": + if !toBool(runInfo.rv) { + runInfo.rv = falseValue + return + } + default: + runInfo.err = newStringError(operator, "unknown operator") + runInfo.rv = nilValue + return + } + + runInfo.expr = operator.RHS + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + if runInfo.rv.Kind() == reflect.Interface && !runInfo.rv.IsNil() { + runInfo.rv = runInfo.rv.Elem() + } + + if toBool(runInfo.rv) { + runInfo.rv = trueValue + } else { + runInfo.rv = falseValue + } + + // ComparisonOperator + case *ast.ComparisonOperator: + runInfo.expr = operator.LHS + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + if runInfo.rv.Kind() == reflect.Interface && !runInfo.rv.IsNil() { + runInfo.rv = runInfo.rv.Elem() + } + lhsV := runInfo.rv + + runInfo.expr = operator.RHS + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + if runInfo.rv.Kind() == reflect.Interface && !runInfo.rv.IsNil() { + runInfo.rv = runInfo.rv.Elem() + } + + switch operator.Operator { + case "==": + runInfo.rv = reflect.ValueOf(equal(lhsV, runInfo.rv)) + case "!=": + runInfo.rv = reflect.ValueOf(!equal(lhsV, runInfo.rv)) + case "<": + runInfo.rv = reflect.ValueOf(toFloat64(lhsV) < toFloat64(runInfo.rv)) + case "<=": + runInfo.rv = reflect.ValueOf(toFloat64(lhsV) <= toFloat64(runInfo.rv)) + case ">": + runInfo.rv = reflect.ValueOf(toFloat64(lhsV) > toFloat64(runInfo.rv)) + case ">=": + runInfo.rv = reflect.ValueOf(toFloat64(lhsV) >= toFloat64(runInfo.rv)) + default: + runInfo.err = newStringError(operator, "unknown operator") + runInfo.rv = nilValue + } + + // AddOperator + case *ast.AddOperator: + runInfo.expr = operator.LHS + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + if runInfo.rv.Kind() == reflect.Interface && !runInfo.rv.IsNil() { + runInfo.rv = runInfo.rv.Elem() + } + lhsV := runInfo.rv + + runInfo.expr = operator.RHS + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + if runInfo.rv.Kind() == reflect.Interface && !runInfo.rv.IsNil() { + runInfo.rv = runInfo.rv.Elem() + } + + switch operator.Operator { + case "+": + lhsKind := lhsV.Kind() + rhsKind := runInfo.rv.Kind() + + if lhsKind == reflect.Slice || lhsKind == reflect.Array { + if rhsKind == reflect.Slice || rhsKind == reflect.Array { + // append slice to slice + runInfo.rv, runInfo.err = appendSlice(operator, lhsV, runInfo.rv) + return + } + // try to append rhs non-slice to lhs slice + runInfo.rv, runInfo.err = convertReflectValueToType(runInfo.rv, lhsV.Type().Elem()) + if runInfo.err != nil { + runInfo.err = newStringError(operator, "invalid type conversion") + runInfo.rv = nilValue + return + } + runInfo.rv = reflect.Append(lhsV, runInfo.rv) + return + } + if rhsKind == reflect.Slice || rhsKind == reflect.Array { + // can not append rhs slice to lhs non-slice + runInfo.err = newStringError(operator, "invalid type conversion") + runInfo.rv = nilValue + return + } + + kind := precedenceOfKinds(lhsKind, rhsKind) + switch kind { + case reflect.String: + runInfo.rv = reflect.ValueOf(toString(lhsV) + toString(runInfo.rv)) + case reflect.Float64, reflect.Float32: + runInfo.rv = reflect.ValueOf(toFloat64(lhsV) + toFloat64(runInfo.rv)) + default: + runInfo.rv = reflect.ValueOf(toInt64(lhsV) + toInt64(runInfo.rv)) + } + + case "-": + switch lhsV.Kind() { + case reflect.Float64, reflect.Float32: + runInfo.rv = reflect.ValueOf(toFloat64(lhsV) - toFloat64(runInfo.rv)) + return + } + switch runInfo.rv.Kind() { + case reflect.Float64, reflect.Float32: + runInfo.rv = reflect.ValueOf(toFloat64(lhsV) - toFloat64(runInfo.rv)) + default: + runInfo.rv = reflect.ValueOf(toInt64(lhsV) - toInt64(runInfo.rv)) + } + + case "|": + runInfo.rv = reflect.ValueOf(toInt64(lhsV) | toInt64(runInfo.rv)) + default: + runInfo.err = newStringError(operator, "unknown operator") + runInfo.rv = nilValue + } + + // MultiplyOperator + case *ast.MultiplyOperator: + runInfo.expr = operator.LHS + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + if runInfo.rv.Kind() == reflect.Interface && !runInfo.rv.IsNil() { + runInfo.rv = runInfo.rv.Elem() + } + lhsV := runInfo.rv + + runInfo.expr = operator.RHS + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + if runInfo.rv.Kind() == reflect.Interface && !runInfo.rv.IsNil() { + runInfo.rv = runInfo.rv.Elem() + } + + switch operator.Operator { + case "*": + if lhsV.Kind() == reflect.String && (runInfo.rv.Kind() == reflect.Int || runInfo.rv.Kind() == reflect.Int32 || runInfo.rv.Kind() == reflect.Int64) { + runInfo.rv = reflect.ValueOf(strings.Repeat(toString(lhsV), int(toInt64(runInfo.rv)))) + return + } + if lhsV.Kind() == reflect.Float64 || runInfo.rv.Kind() == reflect.Float64 { + runInfo.rv = reflect.ValueOf(toFloat64(lhsV) * toFloat64(runInfo.rv)) + return + } + runInfo.rv = reflect.ValueOf(toInt64(lhsV) * toInt64(runInfo.rv)) + case "/": + runInfo.rv = reflect.ValueOf(toFloat64(lhsV) / toFloat64(runInfo.rv)) + case "%": + runInfo.rv = reflect.ValueOf(toInt64(lhsV) % toInt64(runInfo.rv)) + case ">>": + runInfo.rv = reflect.ValueOf(toInt64(lhsV) >> uint64(toInt64(runInfo.rv))) + case "<<": + runInfo.rv = reflect.ValueOf(toInt64(lhsV) << uint64(toInt64(runInfo.rv))) + case "&": + runInfo.rv = reflect.ValueOf(toInt64(lhsV) & toInt64(runInfo.rv)) + + default: + runInfo.err = newStringError(operator, "unknown operator") + runInfo.rv = nilValue + } + + default: + runInfo.err = newStringError(operator, "unknown operator") + runInfo.rv = nilValue + + } +} diff --git a/src/tool/run/vm/vmOperators_test.go b/src/tool/run/vm/vmOperators_test.go new file mode 100644 index 0000000..0d4bdf0 --- /dev/null +++ b/src/tool/run/vm/vmOperators_test.go @@ -0,0 +1,1073 @@ +package vm + +import ( + "fmt" + "reflect" + "testing" +) + +func TestBasicOperators(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `]`, ParseError: fmt.Errorf("syntax error")}, + + {Script: `2 + 1`, RunOutput: int64(3)}, + {Script: `2 - 1`, RunOutput: int64(1)}, + {Script: `2 * 1`, RunOutput: int64(2)}, + {Script: `2 / 1`, RunOutput: float64(2)}, + {Script: `2.1 + 1.1`, RunOutput: float64(3.2)}, + {Script: `2.1 - 1.1`, RunOutput: float64(1)}, + {Script: `2 + 1.1`, RunOutput: float64(3.1)}, + {Script: `2.1 + 1`, RunOutput: float64(3.1)}, + {Script: `3 - 1.5`, RunOutput: float64(1.5)}, + {Script: `2.1 - 1`, RunOutput: float64(1.1)}, + {Script: `2.1 * 2.0`, RunOutput: float64(4.2)}, + {Script: `6.5 / 2.0`, RunOutput: float64(3.25)}, + + {Script: `2-1`, RunOutput: int64(1)}, + {Script: `2 -1`, RunOutput: int64(1)}, + {Script: `2- 1`, RunOutput: int64(1)}, + {Script: `2 - -1`, RunOutput: int64(3)}, + {Script: `2- -1`, RunOutput: int64(3)}, + {Script: `2 - - 1`, RunOutput: int64(3)}, + {Script: `2- - 1`, RunOutput: int64(3)}, + + {Script: `a + b`, Input: map[string]interface{}{"a": int64(2), "b": int64(1)}, RunOutput: int64(3)}, + {Script: `a - b`, Input: map[string]interface{}{"a": int64(2), "b": int64(1)}, RunOutput: int64(1)}, + {Script: `a * b`, Input: map[string]interface{}{"a": int64(2), "b": int64(1)}, RunOutput: int64(2)}, + {Script: `a / b`, Input: map[string]interface{}{"a": int64(2), "b": int64(1)}, RunOutput: float64(2)}, + {Script: `a + b`, Input: map[string]interface{}{"a": float64(2.1), "b": float64(1.1)}, RunOutput: float64(3.2)}, + {Script: `a - b`, Input: map[string]interface{}{"a": float64(2.1), "b": float64(1.1)}, RunOutput: float64(1)}, + {Script: `a * b`, Input: map[string]interface{}{"a": float64(2.1), "b": float64(2)}, RunOutput: float64(4.2)}, + {Script: `a / b`, Input: map[string]interface{}{"a": float64(6.5), "b": float64(2)}, RunOutput: float64(3.25)}, + + {Script: `a + b`, Input: map[string]interface{}{"a": "a", "b": "b"}, RunOutput: "ab"}, + {Script: `a + b`, Input: map[string]interface{}{"a": "a", "b": int64(1)}, RunOutput: "a1"}, + {Script: `a + b`, Input: map[string]interface{}{"a": "a", "b": float64(1.1)}, RunOutput: "a1.1"}, + {Script: `a + b`, Input: map[string]interface{}{"a": int64(2), "b": "b"}, RunOutput: "2b"}, + {Script: `a + b`, Input: map[string]interface{}{"a": float64(2.5), "b": "b"}, RunOutput: "2.5b"}, + + {Script: `a + z`, Input: map[string]interface{}{"a": "a"}, RunError: fmt.Errorf("undefined symbol 'z'"), RunOutput: nil}, + {Script: `z + b`, Input: map[string]interface{}{"a": "a"}, RunError: fmt.Errorf("undefined symbol 'z'"), RunOutput: nil}, + + {Script: `c = a + b`, Input: map[string]interface{}{"a": int64(2), "b": int64(1)}, RunOutput: int64(3), Output: map[string]interface{}{"c": int64(3)}}, + {Script: `c = a - b`, Input: map[string]interface{}{"a": int64(2), "b": int64(1)}, RunOutput: int64(1), Output: map[string]interface{}{"c": int64(1)}}, + {Script: `c = a * b`, Input: map[string]interface{}{"a": int64(2), "b": int64(1)}, RunOutput: int64(2), Output: map[string]interface{}{"c": int64(2)}}, + {Script: `c = a / b`, Input: map[string]interface{}{"a": int64(2), "b": int64(1)}, RunOutput: float64(2), Output: map[string]interface{}{"c": float64(2)}}, + {Script: `c = a + b`, Input: map[string]interface{}{"a": float64(2.1), "b": float64(1.1)}, RunOutput: float64(3.2), Output: map[string]interface{}{"c": float64(3.2)}}, + {Script: `c = a - b`, Input: map[string]interface{}{"a": float64(2.1), "b": float64(1.1)}, RunOutput: float64(1), Output: map[string]interface{}{"c": float64(1)}}, + {Script: `c = a * b`, Input: map[string]interface{}{"a": float64(2.1), "b": float64(2)}, RunOutput: float64(4.2), Output: map[string]interface{}{"c": float64(4.2)}}, + {Script: `c = a / b`, Input: map[string]interface{}{"a": float64(6.5), "b": float64(2)}, RunOutput: float64(3.25), Output: map[string]interface{}{"c": float64(3.25)}}, + + {Script: `a = nil; a++`, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = false; a++`, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = true; a++`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 1; a++`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 1.5; a++`, RunOutput: float64(2.5), Output: map[string]interface{}{"a": float64(2.5)}}, + {Script: `a = "1"; a++`, RunOutput: "11", Output: map[string]interface{}{"a": "11"}}, + {Script: `a = "a"; a++`, RunOutput: "a1", Output: map[string]interface{}{"a": "a1"}}, + + {Script: `a = nil; a--`, RunOutput: int64(-1), Output: map[string]interface{}{"a": int64(-1)}}, + {Script: `a = false; a--`, RunOutput: int64(-1), Output: map[string]interface{}{"a": int64(-1)}}, + {Script: `a = true; a--`, RunOutput: int64(0), Output: map[string]interface{}{"a": int64(0)}}, + {Script: `a = 2; a--`, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 2.5; a--`, RunOutput: float64(1.5), Output: map[string]interface{}{"a": float64(1.5)}}, + + {Script: `a++`, Input: map[string]interface{}{"a": nil}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a++`, Input: map[string]interface{}{"a": false}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a++`, Input: map[string]interface{}{"a": true}, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a++`, Input: map[string]interface{}{"a": int32(1)}, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a++`, Input: map[string]interface{}{"a": int64(1)}, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a++`, Input: map[string]interface{}{"a": float32(3.5)}, RunOutput: float64(4.5), Output: map[string]interface{}{"a": float64(4.5)}}, + {Script: `a++`, Input: map[string]interface{}{"a": float64(4.5)}, RunOutput: float64(5.5), Output: map[string]interface{}{"a": float64(5.5)}}, + {Script: `a++`, Input: map[string]interface{}{"a": "2"}, RunOutput: "21", Output: map[string]interface{}{"a": "21"}}, + {Script: `a++`, Input: map[string]interface{}{"a": "a"}, RunOutput: "a1", Output: map[string]interface{}{"a": "a1"}}, + + {Script: `a--`, Input: map[string]interface{}{"a": nil}, RunOutput: int64(-1), Output: map[string]interface{}{"a": int64(-1)}}, + {Script: `a--`, Input: map[string]interface{}{"a": false}, RunOutput: int64(-1), Output: map[string]interface{}{"a": int64(-1)}}, + {Script: `a--`, Input: map[string]interface{}{"a": true}, RunOutput: int64(0), Output: map[string]interface{}{"a": int64(0)}}, + {Script: `a--`, Input: map[string]interface{}{"a": int32(2)}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a--`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a--`, Input: map[string]interface{}{"a": float32(2.5)}, RunOutput: float64(1.5), Output: map[string]interface{}{"a": float64(1.5)}}, + {Script: `a--`, Input: map[string]interface{}{"a": float64(2.5)}, RunOutput: float64(1.5), Output: map[string]interface{}{"a": float64(1.5)}}, + {Script: `a--`, Input: map[string]interface{}{"a": "2"}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a--`, Input: map[string]interface{}{"a": "a"}, RunOutput: int64(-1), Output: map[string]interface{}{"a": int64(-1)}}, + + {Script: `1++`, RunError: fmt.Errorf("invalid operation"), RunOutput: nil}, + {Script: `1--`, RunError: fmt.Errorf("invalid operation"), RunOutput: nil}, + {Script: `z++`, RunError: fmt.Errorf("undefined symbol 'z'"), RunOutput: nil}, + {Script: `z--`, RunError: fmt.Errorf("undefined symbol 'z'"), RunOutput: nil}, + {Script: `!(1++)`, RunError: fmt.Errorf("invalid operation"), RunOutput: nil}, + {Script: `1 + 1++`, RunError: fmt.Errorf("invalid operation"), RunOutput: nil}, + {Script: `1 - 1++`, RunError: fmt.Errorf("invalid operation"), RunOutput: nil}, + {Script: `1 * 1++`, RunError: fmt.Errorf("invalid operation"), RunOutput: nil}, + {Script: `1 / 1++`, RunError: fmt.Errorf("invalid operation"), RunOutput: nil}, + {Script: `1++ + 1`, RunError: fmt.Errorf("invalid operation"), RunOutput: nil}, + {Script: `1++ - 1`, RunError: fmt.Errorf("invalid operation"), RunOutput: nil}, + {Script: `1++ * 1`, RunError: fmt.Errorf("invalid operation"), RunOutput: nil}, + {Script: `1++ / 1`, RunError: fmt.Errorf("invalid operation"), RunOutput: nil}, + + {Script: `a = 1; b = 2; a + b`, RunOutput: int64(3)}, + {Script: `a = [1]; b = 2; a[0] + b`, RunOutput: int64(3)}, + {Script: `a = 1; b = [2]; a + b[0]`, RunOutput: int64(3)}, + {Script: `a = 2; b = 1; a - b`, RunOutput: int64(1)}, + {Script: `a = [2]; b = 1; a[0] - b`, RunOutput: int64(1)}, + {Script: `a = 2; b = [1]; a - b[0]`, RunOutput: int64(1)}, + {Script: `a = 1; b = 2; a * b`, RunOutput: int64(2)}, + {Script: `a = [1]; b = 2; a[0] * b`, RunOutput: int64(2)}, + {Script: `a = 1; b = [2]; a * b[0]`, RunOutput: int64(2)}, + {Script: `a = 4; b = 2; a / b`, RunOutput: float64(2)}, + {Script: `a = [4]; b = 2; a[0] / b`, RunOutput: float64(2)}, + {Script: `a = 4; b = [2]; a / b[0]`, RunOutput: float64(2)}, + + {Script: `a += 1`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(3), Output: map[string]interface{}{"a": int64(3)}}, + {Script: `a -= 1`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a *= 2`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(4), Output: map[string]interface{}{"a": int64(4)}}, + {Script: `a /= 2`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: float64(1), Output: map[string]interface{}{"a": float64(1)}}, + {Script: `a += 1`, Input: map[string]interface{}{"a": 2.1}, RunOutput: float64(3.1), Output: map[string]interface{}{"a": float64(3.1)}}, + {Script: `a -= 1`, Input: map[string]interface{}{"a": 2.1}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `a *= 2`, Input: map[string]interface{}{"a": 2.1}, RunOutput: float64(4.2), Output: map[string]interface{}{"a": float64(4.2)}}, + {Script: `a /= 2`, Input: map[string]interface{}{"a": 6.5}, RunOutput: float64(3.25), Output: map[string]interface{}{"a": float64(3.25)}}, + + {Script: `a &= 1`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(0), Output: map[string]interface{}{"a": int64(0)}}, + {Script: `a &= 2`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a &= 1`, Input: map[string]interface{}{"a": float64(2.1)}, RunOutput: int64(0), Output: map[string]interface{}{"a": int64(0)}}, + {Script: `a &= 2`, Input: map[string]interface{}{"a": float64(2.1)}, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + + {Script: `a |= 1`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(3), Output: map[string]interface{}{"a": int64(3)}}, + {Script: `a |= 2`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a |= 1`, Input: map[string]interface{}{"a": float64(2.1)}, RunOutput: int64(3), Output: map[string]interface{}{"a": int64(3)}}, + {Script: `a |= 2`, Input: map[string]interface{}{"a": float64(2.1)}, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + + {Script: `a << 2`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(8), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a >> 2`, Input: map[string]interface{}{"a": int64(8)}, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(8)}}, + {Script: `a << 2`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: int64(8), Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a >> 2`, Input: map[string]interface{}{"a": float64(8)}, RunOutput: int64(2), Output: map[string]interface{}{"a": float64(8)}}, + + {Script: `a % 2`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(0), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a % 3`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a % 2`, Input: map[string]interface{}{"a": float64(2.1)}, RunOutput: int64(0), Output: map[string]interface{}{"a": float64(2.1)}}, + {Script: `a % 3`, Input: map[string]interface{}{"a": float64(2.1)}, RunOutput: int64(2), Output: map[string]interface{}{"a": float64(2.1)}}, + + {Script: `a * 4`, Input: map[string]interface{}{"a": "a"}, RunOutput: "aaaa", Output: map[string]interface{}{"a": "a"}}, + {Script: `a * 4.0`, Input: map[string]interface{}{"a": "a"}, RunOutput: float64(0), Output: map[string]interface{}{"a": "a"}}, + + {Script: `-a`, Input: map[string]interface{}{"a": nil}, RunOutput: float64(-0), Output: map[string]interface{}{"a": nil}}, + {Script: `-a`, Input: map[string]interface{}{"a": int32(1)}, RunOutput: int64(-1), Output: map[string]interface{}{"a": int32(1)}}, + {Script: `-a`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(-2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `-a`, Input: map[string]interface{}{"a": float32(3.5)}, RunOutput: float64(-3.5), Output: map[string]interface{}{"a": float32(3.5)}}, + {Script: `-a`, Input: map[string]interface{}{"a": float64(4.5)}, RunOutput: float64(-4.5), Output: map[string]interface{}{"a": float64(4.5)}}, + {Script: `-a`, Input: map[string]interface{}{"a": "a"}, RunOutput: float64(-0), Output: map[string]interface{}{"a": "a"}}, + {Script: `-a`, Input: map[string]interface{}{"a": "1"}, RunOutput: float64(-1), Output: map[string]interface{}{"a": "1"}}, + + {Script: `^a`, Input: map[string]interface{}{"a": nil}, RunOutput: int64(-1), Output: map[string]interface{}{"a": nil}}, + {Script: `^a`, Input: map[string]interface{}{"a": "a"}, RunOutput: int64(-1), Output: map[string]interface{}{"a": "a"}}, + {Script: `^a`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(-3), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `^a`, Input: map[string]interface{}{"a": float64(2.1)}, RunOutput: int64(-3), Output: map[string]interface{}{"a": float64(2.1)}}, + + {Script: `!true`, RunOutput: false}, + {Script: `!false`, RunOutput: true}, + {Script: `!1`, RunOutput: false}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestComparisonOperators(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `1++ == 2`, RunError: fmt.Errorf("invalid operation"), RunOutput: nil}, + {Script: `2 == 1++`, RunError: fmt.Errorf("invalid operation"), RunOutput: nil}, + {Script: `1++ || true`, RunError: fmt.Errorf("invalid operation"), RunOutput: nil}, + {Script: `false || 1++`, RunError: fmt.Errorf("invalid operation"), RunOutput: nil}, + {Script: `1++ && true`, RunError: fmt.Errorf("invalid operation"), RunOutput: nil}, + {Script: `true && 1++`, RunError: fmt.Errorf("invalid operation"), RunOutput: nil}, + + {Script: `a == 1`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: false, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a == 2`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: true, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a != 1`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: true, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a != 2`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: false, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a == 1.0`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: false, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a == 2.0`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: true, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a != 1.0`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: true, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a != 2.0`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: false, Output: map[string]interface{}{"a": int64(2)}}, + + {Script: `a == 1`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: false, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a == 2`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: true, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a != 1`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: true, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a != 2`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: false, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a == 1.0`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: false, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a == 2.0`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: true, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a != 1.0`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: true, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a != 2.0`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: false, Output: map[string]interface{}{"a": float64(2)}}, + + {Script: `a == nil`, Input: map[string]interface{}{"a": nil}, RunOutput: true, Output: map[string]interface{}{"a": nil}}, + {Script: `a == nil`, Input: map[string]interface{}{"a": nil}, RunOutput: true, Output: map[string]interface{}{"a": nil}}, + {Script: `a == nil`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: false, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a == nil`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: false, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a == 2`, Input: map[string]interface{}{"a": nil}, RunOutput: false, Output: map[string]interface{}{"a": nil}}, + {Script: `a == 2.0`, Input: map[string]interface{}{"a": nil}, RunOutput: false, Output: map[string]interface{}{"a": nil}}, + + {Script: `1 == 1.0`, RunOutput: true}, + {Script: `1 != 1.0`, RunOutput: false}, + {Script: `"a" != "a"`, RunOutput: false}, + + {Script: `a > 2`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: false, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a > 1`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: true, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a < 2`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: false, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a < 3`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: true, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a > 2.0`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: false, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a > 1.0`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: true, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a < 2.0`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: false, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a < 3.0`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: true, Output: map[string]interface{}{"a": int64(2)}}, + + {Script: `a > 2`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: false, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a > 1`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: true, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a < 2`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: false, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a < 3`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: true, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a > 2.0`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: false, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a > 1.0`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: true, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a < 2.0`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: false, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a < 3.0`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: true, Output: map[string]interface{}{"a": float64(2)}}, + + {Script: `a >= 2`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: true, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a >= 3`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: false, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a <= 2`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: true, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a <= 3`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: true, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a >= 2.0`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: true, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a >= 3.0`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: false, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a <= 2.0`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: true, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a <= 3.0`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: true, Output: map[string]interface{}{"a": int64(2)}}, + + {Script: `a >= 2`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: true, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a >= 3`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: false, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a <= 2`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: true, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a <= 3`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: true, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a >= 2.0`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: true, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a >= 3.0`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: false, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a <= 2.0`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: true, Output: map[string]interface{}{"a": float64(2)}}, + {Script: `a <= 3.0`, Input: map[string]interface{}{"a": float64(2)}, RunOutput: true, Output: map[string]interface{}{"a": float64(2)}}, + + {Script: `a = false; b = false; a && b`, RunOutput: false}, + {Script: `a = false; b = true; a && b`, RunOutput: false}, + {Script: `a = true; b = false; a && b`, RunOutput: false}, + {Script: `a = true; b = true; a && b`, RunOutput: true}, + {Script: `a = false; b = false; a || b`, RunOutput: false}, + {Script: `a = false; b = true; a || b`, RunOutput: true}, + {Script: `a = true; b = false; a || b`, RunOutput: true}, + {Script: `a = true; b = true; a || b`, RunOutput: true}, + + {Script: `a = [false]; b = false; a[0] && b`, RunOutput: false}, + {Script: `a = [false]; b = true; a[0] && b`, RunOutput: false}, + {Script: `a = [true]; b = false; a[0] && b`, RunOutput: false}, + {Script: `a = [true]; b = true; a[0] && b`, RunOutput: true}, + {Script: `a = [false]; b = false; a[0] || b`, RunOutput: false}, + {Script: `a = [false]; b = true; a[0] || b`, RunOutput: true}, + {Script: `a = [true]; b = false; a[0] || b`, RunOutput: true}, + {Script: `a = [true]; b = true; a[0] || b`, RunOutput: true}, + + {Script: `a = false; b = [false]; a && b[0]`, RunOutput: false}, + {Script: `a = false; b = [true]; a && b[0]`, RunOutput: false}, + {Script: `a = true; b = [false]; a && b[0]`, RunOutput: false}, + {Script: `a = true; b = [true]; a && b[0]`, RunOutput: true}, + {Script: `a = false; b = [false]; a || b[0]`, RunOutput: false}, + {Script: `a = false; b = [true]; a || b[0]`, RunOutput: true}, + {Script: `a = true; b = [false]; a || b[0]`, RunOutput: true}, + {Script: `a = true; b = [true]; a || b[0]`, RunOutput: true}, + + {Script: `0 && 0`, RunOutput: false}, + {Script: `0 && 1`, RunOutput: false}, + {Script: `1 && 0`, RunOutput: false}, + {Script: `1 && 1`, RunOutput: true}, + {Script: `0 || 0`, RunOutput: false}, + {Script: `0 || 1`, RunOutput: true}, + {Script: `1 || 0`, RunOutput: true}, + {Script: `1 || 1`, RunOutput: true}, + + {Script: `1 == 1 && 1 == 1`, RunOutput: true}, + {Script: `1 == 1 && 1 == 2`, RunOutput: false}, + {Script: `1 == 2 && 1 == 1`, RunOutput: false}, + {Script: `1 == 2 && 1 == 2`, RunOutput: false}, + {Script: `1 == 1 || 1 == 1`, RunOutput: true}, + {Script: `1 == 1 || 1 == 2`, RunOutput: true}, + {Script: `1 == 2 || 1 == 1`, RunOutput: true}, + {Script: `1 == 2 || 1 == 2`, RunOutput: false}, + + {Script: `true == "1"`, RunOutput: true}, + {Script: `true == "t"`, RunOutput: true}, + {Script: `true == "T"`, RunOutput: true}, + {Script: `true == "true"`, RunOutput: true}, + {Script: `true == "TRUE"`, RunOutput: true}, + {Script: `true == "True"`, RunOutput: true}, + {Script: `true == "false"`, RunOutput: false}, + {Script: `false == "0"`, RunOutput: true}, + {Script: `false == "f"`, RunOutput: true}, + {Script: `false == "F"`, RunOutput: true}, + {Script: `false == "false"`, RunOutput: true}, + {Script: `false == "false"`, RunOutput: true}, + {Script: `false == "FALSE"`, RunOutput: true}, + {Script: `false == "False"`, RunOutput: true}, + {Script: `false == "true"`, RunOutput: false}, + {Script: `false == "foo"`, RunOutput: false}, + {Script: `true == "foo"`, RunOutput: true}, + + {Script: `0 == "0"`, RunOutput: true}, + {Script: `"1.0" == 1`, RunOutput: true}, + {Script: `1 == "1"`, RunOutput: true}, + {Script: `0.0 == "0"`, RunOutput: true}, + {Script: `0.0 == "0.0"`, RunOutput: true}, + {Script: `1.0 == "1.0"`, RunOutput: true}, + {Script: `1.2 == "1.2"`, RunOutput: true}, + {Script: `"7" == 7.2`, RunOutput: false}, + {Script: `1.2 == "1"`, RunOutput: false}, + {Script: `"1.1" == 1`, RunOutput: false}, + {Script: `0 == "1"`, RunOutput: false}, + + {Script: `a == b`, Input: map[string]interface{}{"a": reflect.Value{}, "b": reflect.Value{}}, RunOutput: true, Output: map[string]interface{}{"a": reflect.Value{}, "b": reflect.Value{}}}, + {Script: `a == b`, Input: map[string]interface{}{"a": reflect.Value{}, "b": true}, RunOutput: false, Output: map[string]interface{}{"a": reflect.Value{}, "b": true}}, + {Script: `a == b`, Input: map[string]interface{}{"a": true, "b": reflect.Value{}}, RunOutput: false, Output: map[string]interface{}{"a": true, "b": reflect.Value{}}}, + + {Script: `a == b`, Input: map[string]interface{}{"a": nil, "b": nil}, RunOutput: true, Output: map[string]interface{}{"a": nil, "b": nil}}, + {Script: `a == b`, Input: map[string]interface{}{"a": nil, "b": true}, RunOutput: false, Output: map[string]interface{}{"a": nil, "b": true}}, + {Script: `a == b`, Input: map[string]interface{}{"a": true, "b": nil}, RunOutput: false, Output: map[string]interface{}{"a": true, "b": nil}}, + + {Script: `a == b`, Input: map[string]interface{}{"a": false, "b": false}, RunOutput: true, Output: map[string]interface{}{"a": false, "b": false}}, + {Script: `a == b`, Input: map[string]interface{}{"a": false, "b": true}, RunOutput: false, Output: map[string]interface{}{"a": false, "b": true}}, + {Script: `a == b`, Input: map[string]interface{}{"a": true, "b": false}, RunOutput: false, Output: map[string]interface{}{"a": true, "b": false}}, + {Script: `a == b`, Input: map[string]interface{}{"a": true, "b": true}, RunOutput: true, Output: map[string]interface{}{"a": true, "b": true}}, + + {Script: `a == b`, Input: map[string]interface{}{"a": int32(1), "b": int32(1)}, RunOutput: true, Output: map[string]interface{}{"a": int32(1), "b": int32(1)}}, + {Script: `a == b`, Input: map[string]interface{}{"a": int32(1), "b": int32(2)}, RunOutput: false, Output: map[string]interface{}{"a": int32(1), "b": int32(2)}}, + {Script: `a == b`, Input: map[string]interface{}{"a": int32(2), "b": int32(1)}, RunOutput: false, Output: map[string]interface{}{"a": int32(2), "b": int32(1)}}, + {Script: `a == b`, Input: map[string]interface{}{"a": int32(2), "b": int32(2)}, RunOutput: true, Output: map[string]interface{}{"a": int32(2), "b": int32(2)}}, + + {Script: `a == b`, Input: map[string]interface{}{"a": int64(1), "b": int64(1)}, RunOutput: true, Output: map[string]interface{}{"a": int64(1), "b": int64(1)}}, + {Script: `a == b`, Input: map[string]interface{}{"a": int64(1), "b": int64(2)}, RunOutput: false, Output: map[string]interface{}{"a": int64(1), "b": int64(2)}}, + {Script: `a == b`, Input: map[string]interface{}{"a": int64(2), "b": int64(1)}, RunOutput: false, Output: map[string]interface{}{"a": int64(2), "b": int64(1)}}, + {Script: `a == b`, Input: map[string]interface{}{"a": int64(2), "b": int64(2)}, RunOutput: true, Output: map[string]interface{}{"a": int64(2), "b": int64(2)}}, + + {Script: `a == b`, Input: map[string]interface{}{"a": float32(1.1), "b": float32(1.1)}, RunOutput: true, Output: map[string]interface{}{"a": float32(1.1), "b": float32(1.1)}}, + {Script: `a == b`, Input: map[string]interface{}{"a": float32(1.1), "b": float32(2.2)}, RunOutput: false, Output: map[string]interface{}{"a": float32(1.1), "b": float32(2.2)}}, + {Script: `a == b`, Input: map[string]interface{}{"a": float32(2.2), "b": float32(1.1)}, RunOutput: false, Output: map[string]interface{}{"a": float32(2.2), "b": float32(1.1)}}, + {Script: `a == b`, Input: map[string]interface{}{"a": float32(2.2), "b": float32(2.2)}, RunOutput: true, Output: map[string]interface{}{"a": float32(2.2), "b": float32(2.2)}}, + + {Script: `a == b`, Input: map[string]interface{}{"a": float64(1.1), "b": float64(1.1)}, RunOutput: true, Output: map[string]interface{}{"a": float64(1.1), "b": float64(1.1)}}, + {Script: `a == b`, Input: map[string]interface{}{"a": float64(1.1), "b": float64(2.2)}, RunOutput: false, Output: map[string]interface{}{"a": float64(1.1), "b": float64(2.2)}}, + {Script: `a == b`, Input: map[string]interface{}{"a": float64(2.2), "b": float64(1.1)}, RunOutput: false, Output: map[string]interface{}{"a": float64(2.2), "b": float64(1.1)}}, + {Script: `a == b`, Input: map[string]interface{}{"a": float64(2.2), "b": float64(2.2)}, RunOutput: true, Output: map[string]interface{}{"a": float64(2.2), "b": float64(2.2)}}, + + {Script: `a == b`, Input: map[string]interface{}{"a": 'a', "b": 'a'}, RunOutput: true, Output: map[string]interface{}{"a": 'a', "b": 'a'}}, + {Script: `a == b`, Input: map[string]interface{}{"a": 'a', "b": 'b'}, RunOutput: false, Output: map[string]interface{}{"a": 'a', "b": 'b'}}, + {Script: `a == b`, Input: map[string]interface{}{"a": 'b', "b": 'a'}, RunOutput: false, Output: map[string]interface{}{"a": 'b', "b": 'a'}}, + {Script: `a == b`, Input: map[string]interface{}{"a": 'b', "b": 'b'}, RunOutput: true, Output: map[string]interface{}{"a": 'b', "b": 'b'}}, + + {Script: `a == b`, Input: map[string]interface{}{"a": "a", "b": "a"}, RunOutput: true, Output: map[string]interface{}{"a": "a", "b": "a"}}, + {Script: `a == b`, Input: map[string]interface{}{"a": "a", "b": "b"}, RunOutput: false, Output: map[string]interface{}{"a": "a", "b": "b"}}, + {Script: `a == b`, Input: map[string]interface{}{"a": "b", "b": "a"}, RunOutput: false, Output: map[string]interface{}{"a": "b", "b": "a"}}, + {Script: `a == b`, Input: map[string]interface{}{"a": "b", "b": "b"}, RunOutput: true, Output: map[string]interface{}{"a": "b", "b": "b"}}, + + {Script: `b = "\"a\""; a == b`, Input: map[string]interface{}{"a": `"a"`}, RunOutput: true, Output: map[string]interface{}{"a": `"a"`, "b": `"a"`}}, + + {Script: `a = "test"; a == "test"`, RunOutput: true}, + {Script: `a = "test"; a[0:1] == "t"`, RunOutput: true}, + {Script: `a = "test"; a[0:2] == "te"`, RunOutput: true}, + {Script: `a = "test"; a[1:3] == "es"`, RunOutput: true}, + {Script: `a = "test"; a[0:4] == "test"`, RunOutput: true}, + + {Script: `a = "a b"; a[1] == ' '`, RunOutput: true}, + {Script: `a = "test"; a[0] == 't'`, RunOutput: true}, + {Script: `a = "test"; a[1] == 'e'`, RunOutput: true}, + {Script: `a = "test"; a[3] == 't'`, RunOutput: true}, + + {Script: `a = "a b"; a[1] != ' '`, RunOutput: false}, + {Script: `a = "test"; a[0] != 't'`, RunOutput: false}, + {Script: `a = "test"; a[1] != 'e'`, RunOutput: false}, + {Script: `a = "test"; a[3] != 't'`, RunOutput: false}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestThrows(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `throw(1++)`, RunError: fmt.Errorf("invalid operation")}, + // {Script: `throw(a)`, Input: map[string]interface{}{"a": reflect.Value{}}, RunError: fmt.Errorf("invalid operation")}, + + {Script: `true && func(){throw('abcde')}()`, RunError: fmt.Errorf("abcde")}, + {Script: `false && func(){throw('abcde')}()`, RunOutput: false}, + {Script: `true || func(){throw('abcde')}()`, RunOutput: true}, + {Script: `false || func(){throw('abcde')}()`, RunError: fmt.Errorf("abcde")}, + {Script: `true && true && func(){throw('abcde')}()`, RunError: fmt.Errorf("abcde")}, + {Script: `true && false && func(){throw('abcde')}()`, RunOutput: false}, + {Script: `true && func(){throw('abcde')}() && true`, RunError: fmt.Errorf("abcde")}, + {Script: `false && func(){throw('abcde')}() && func(){throw('abcde')}() `, RunOutput: false}, + + {Script: `true && func(){throw('abcde')}() || false`, RunError: fmt.Errorf("abcde")}, + {Script: `true && false || func(){throw('abcde')}()`, RunError: fmt.Errorf("abcde")}, + {Script: `true && true || func(){throw('abcde')}()`, RunOutput: true}, + + {Script: `true || func(){throw('abcde')}() || func(){throw('abcde')}()`, RunOutput: true}, + {Script: `false || func(){throw('abcde')}() || true`, RunError: fmt.Errorf("abcde")}, + {Script: `false || true || func(){throw('abcde')}()`, RunOutput: true}, + {Script: `false || false || func(){throw('abcde')}()`, RunError: fmt.Errorf("abcde")}, + + {Script: `false || false && func(){throw('abcde')}()`, RunOutput: false}, + {Script: `false || true && func(){throw('abcde')}()`, RunError: fmt.Errorf("abcde")}, + {Script: `false || func(){throw('abcde')}() || true`, RunError: fmt.Errorf("abcde")}, + + {Script: `1 == 1 && func(){throw('abcde')}()`, RunError: fmt.Errorf("abcde")}, + {Script: `1 == 2 && func(){throw('abcde')}()`, RunOutput: false}, + {Script: `1 == 1 || func(){throw('abcde')}()`, RunOutput: true}, + {Script: `1 == 2 || func(){throw('abcde')}()`, RunError: fmt.Errorf("abcde")}, + + {Script: `(true || func(){throw('abcde')}()) && (true || func(){throw('hello')}())`, RunOutput: true}, + {Script: `(true || func(){throw('abcde')}()) && (true && func(){throw('hello')}())`, RunError: fmt.Errorf("hello")}, + {Script: `(true || func(){throw('abcde')}()) || (true && func(){throw('hello')}())`, RunOutput: true}, + {Script: `(true && func(){throw('abcde')}()) && (true && func(){throw('hello')}())`, RunError: fmt.Errorf("abcde")}, + {Script: `(true || func(){throw('abcde')}()) && (false || func(){throw('hello')}())`, RunError: fmt.Errorf("hello")}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestTernaryOperator(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `a = a ? 1 : 2`, RunError: fmt.Errorf("undefined symbol 'a'")}, + {Script: `a = z ? 1 : 2`, RunError: fmt.Errorf("undefined symbol 'z'")}, + {Script: `a = 0; a = a ? 1 : z`, RunError: fmt.Errorf("undefined symbol 'z'")}, + {Script: `a = 1; a = a ? z : 1`, RunError: fmt.Errorf("undefined symbol 'z'")}, + {Script: `a = b[1] ? 2 : 1`, Input: map[string]interface{}{"b": []interface{}{}}, RunError: fmt.Errorf("index out of range")}, + {Script: `a = b[1][2] ? 2 : 1`, Input: map[string]interface{}{"b": []interface{}{}}, RunError: fmt.Errorf("index out of range")}, + {Script: `a = b["test"][1] ? 2 : 1`, Input: map[string]interface{}{"b": map[string]interface{}{"test": 2}}, RunError: fmt.Errorf("type int does not support index operation")}, + + {Script: `a = 1 ? 2 : z`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = -1 ? 2 : 1`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = true ? 2 : 1`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = false ? 2 : 1`, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = "true" ? 2 : 1`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = "false" ? 2 : 1`, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = "-1" ? 2 : 1`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = "0" ? 2 : 1`, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = "0.0" ? 2 : 1`, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = "2" ? 2 : 1`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = b ? 2 : 1`, Input: map[string]interface{}{"b": int64(0)}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = b ? 2 : 1`, Input: map[string]interface{}{"b": int64(2)}, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = b ? 2 : 1`, Input: map[string]interface{}{"b": float64(0.0)}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = b ? 2 : 1`, Input: map[string]interface{}{"b": float64(2.0)}, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = b ? 2 : 1.0`, Input: map[string]interface{}{"b": float64(0.0)}, RunOutput: float64(1.0), Output: map[string]interface{}{"a": float64(1.0)}}, + {Script: `a = b ? 2 : 1.0`, Input: map[string]interface{}{"b": float64(0.1)}, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = b ? 2 : 1.0`, Input: map[string]interface{}{"b": nil}, RunOutput: float64(1.0), Output: map[string]interface{}{"a": float64(1.0)}}, + {Script: `a = nil ? 2 : 1`, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = b ? 2 : 1`, Input: map[string]interface{}{"b": []interface{}{}}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = b ? 2 : 1`, Input: map[string]interface{}{"b": map[string]interface{}{}}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = [] ? 2 : 1`, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = [2] ? 2 : 1`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = b ? 2 : 1`, Input: map[string]interface{}{"b": map[string]interface{}{"test": int64(2)}}, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = b["test"] ? 2 : 1`, Input: map[string]interface{}{"b": map[string]interface{}{"test": int64(2)}}, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `b = "test"; a = b ? 2 : "empty"`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `b = "test"; a = b[1:3] ? 2 : "empty"`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `b = "test"; a = b[2:2] ? 2 : "empty"`, RunOutput: "empty", Output: map[string]interface{}{"a": "empty"}}, + {Script: `b = "0.0"; a = false ? 2 : b ? 3 : 1`, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `b = "true"; a = false ? 2 : b ? 3 : 1`, RunOutput: int64(3), Output: map[string]interface{}{"a": int64(3)}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestNilCoalescingOperator(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `nil ?? nil`, RunOutput: nil}, + {Script: `false ?? nil`, RunOutput: false}, + {Script: `true ?? nil`, RunOutput: true}, + {Script: `nil ?? false`, RunOutput: false}, + {Script: `nil ?? true`, RunOutput: true}, + {Script: `1 ?? nil`, RunOutput: int64(1)}, + {Script: `1 ?? 2`, RunOutput: int64(1)}, + {Script: `nil ?? 1`, RunOutput: int64(1)}, + + {Script: `a ?? 1`, RunOutput: int64(1)}, + {Script: `a ?? b`, RunError: fmt.Errorf("undefined symbol 'b'")}, + + {Script: `a ?? 2`, Input: map[string]interface{}{"a": int64(1)}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a ?? b`, Input: map[string]interface{}{"a": int64(1)}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a ?? b`, Input: map[string]interface{}{"a": int64(1), "b": int64(2)}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1), "b": int64(2)}}, + {Script: `a ?? b`, Input: map[string]interface{}{"a": nil, "b": int64(2)}, RunOutput: int64(2), Output: map[string]interface{}{"a": nil, "b": int64(2)}}, + + {Script: `[] ?? 1`, RunOutput: []interface{}{}}, + {Script: `{} ?? 1`, RunOutput: map[interface{}]interface{}{}}, + + // test nil array and map + {Script: `a ?? 5`, Input: map[string]interface{}{"a": testSliceEmpty}, RunOutput: int64(5), Output: map[string]interface{}{"a": testSliceEmpty}}, + {Script: `a ?? 6`, Input: map[string]interface{}{"a": testMapEmpty}, RunOutput: int64(6), Output: map[string]interface{}{"a": testMapEmpty}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestIf(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `if 1++ {}`, RunError: fmt.Errorf("invalid operation")}, + {Script: `if false {} else if 1++ {}`, RunError: fmt.Errorf("invalid operation")}, + {Script: `if false {} else if true { 1++ }`, RunError: fmt.Errorf("invalid operation")}, + + {Script: `if true {}`, Input: map[string]interface{}{"a": nil}, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + {Script: `if true {}`, Input: map[string]interface{}{"a": true}, RunOutput: nil, Output: map[string]interface{}{"a": true}}, + {Script: `if true {}`, Input: map[string]interface{}{"a": int64(1)}, RunOutput: nil, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `if true {}`, Input: map[string]interface{}{"a": float64(1.1)}, RunOutput: nil, Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `if true {}`, Input: map[string]interface{}{"a": "a"}, RunOutput: nil, Output: map[string]interface{}{"a": "a"}}, + + {Script: `if true {a = nil}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + {Script: `if true {a = true}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: true, Output: map[string]interface{}{"a": true}}, + {Script: `if true {a = 1}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `if true {a = 1.1}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `if true {a = "a"}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: "a", Output: map[string]interface{}{"a": "a"}}, + + {Script: `if a == 1 {a = 1}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: false, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `if a == 2 {a = 1}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `if a == 1 {a = nil}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: false, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `if a == 2 {a = nil}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + + {Script: `if a == 1 {a = 1} else {a = 3}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(3), Output: map[string]interface{}{"a": int64(3)}}, + {Script: `if a == 2 {a = 1} else {a = 3}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `if a == 1 {a = 1} else if a == 3 {a = 3}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: false, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `if a == 1 {a = 1} else if a == 2 {a = 3}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(3), Output: map[string]interface{}{"a": int64(3)}}, + {Script: `if a == 1 {a = 1} else if a == 3 {a = 3} else {a = 4}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(4), Output: map[string]interface{}{"a": int64(4)}}, + + {Script: `if a == 1 {a = 1} else {a = nil}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + {Script: `if a == 2 {a = nil} else {a = 3}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + {Script: `if a == 1 {a = nil} else if a == 3 {a = nil}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: false, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `if a == 1 {a = 1} else if a == 2 {a = nil}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + {Script: `if a == 1 {a = 1} else if a == 3 {a = 3} else {a = nil}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + + {Script: `if a == 1 {a = 1} else if a == 3 {a = 3} else if a == 4 {a = 4} else {a = 5}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(5)}}, + {Script: `if a == 1 {a = 1} else if a == 3 {a = 3} else if a == 4 {a = 4} else {a = nil}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + {Script: `if a == 1 {a = 1} else if a == 3 {a = 3} else if a == 2 {a = 4} else {a = 5}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: int64(4), Output: map[string]interface{}{"a": int64(4)}}, + {Script: `if a == 1 {a = 1} else if a == 3 {a = 3} else if a == 2 {a = nil} else {a = 5}`, Input: map[string]interface{}{"a": int64(2)}, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + + // check scope + {Script: `a = 1; if a == 1 { b = 2 }; b`, RunError: fmt.Errorf("undefined symbol 'b'"), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; if a == 2 { b = 3 } else { b = 4 }; b`, RunError: fmt.Errorf("undefined symbol 'b'"), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; if a == 2 { b = 3 } else if a == 1 { b = 4 }; b`, RunError: fmt.Errorf("undefined symbol 'b'"), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; if a == 2 { b = 4 } else if a == 5 { b = 6 } else if a == 1 { c = b }`, RunError: fmt.Errorf("undefined symbol 'b'"), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; if a == 2 { b = 4 } else if a == 5 { b = 6 } else if a == 1 { b = 7 }; b`, RunError: fmt.Errorf("undefined symbol 'b'"), Output: map[string]interface{}{"a": int64(1)}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestSwitch(t *testing.T) { + t.Parallel() + + tests := []Test{ + // test parse errors + {Script: `switch {}`, ParseError: fmt.Errorf("syntax error")}, + {Script: `a = 1; switch a; {}`, ParseError: fmt.Errorf("syntax error")}, + {Script: `a = 1; switch a = 2 {}`, ParseError: fmt.Errorf("syntax error")}, + {Script: `a = 1; switch a {default: return 6; default: return 7}`, ParseError: fmt.Errorf("multiple default statement"), RunOutput: int64(7)}, + {Script: `a = 1; switch a {case 1: return 5; default: return 6; default: return 7}`, ParseError: fmt.Errorf("multiple default statement"), RunOutput: int64(5)}, + + // test run errors + {Script: `a = 1; switch 1++ {}`, RunError: fmt.Errorf("invalid operation")}, + {Script: `a = 1; switch a {case 1++: return 2}`, RunError: fmt.Errorf("invalid operation")}, + + // test no or empty cases + {Script: `a = 1; switch a {}`, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; switch a {case: return 2}`, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; switch a {case: return 2; case: return 3}`, Output: map[string]interface{}{"a": int64(1)}}, + + // test 1 case + {Script: `a = 1; switch a {case 1: return 5}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 2; switch a {case 1: return 5}`, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 1; switch a {case 1,2: return 5}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 2; switch a {case 1,2: return 5}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 3; switch a {case 1,2: return 5}`, Output: map[string]interface{}{"a": int64(3)}}, + {Script: `a = 1; switch a {case 1,2,3: return 5}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 2; switch a {case 1,2,3: return 5}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 3; switch a {case 1,2,3: return 5}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(3)}}, + {Script: `a = 4; switch a {case 1,2,3: return 5}`, Output: map[string]interface{}{"a": int64(4)}}, + {Script: `a = func() { return 1 }; switch a() {case 1: return 5}`, RunOutput: int64(5)}, + {Script: `a = func() { return 2 }; switch a() {case 1: return 5}`}, + {Script: `a = func() { return 5 }; b = 1; switch b {case 1: return a() }`, RunOutput: int64(5), Output: map[string]interface{}{"b": int64(1)}}, + {Script: `a = func() { return 6 }; b = 2; switch b {case 1: return a() }`, Output: map[string]interface{}{"b": int64(2)}}, + + // test 2 cases + {Script: `a = 1; switch a {case 1: return 5; case 2: return 6}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 2; switch a {case 1: return 5; case 2: return 6}`, RunOutput: int64(6), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 3; switch a {case 1: return 5; case 2: return 6}`, Output: map[string]interface{}{"a": int64(3)}}, + {Script: `a = 1; switch a {case 1: return 5; case 2,3: return 6}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 2; switch a {case 1: return 5; case 2,3: return 6}`, RunOutput: int64(6), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 3; switch a {case 1: return 5; case 2,3: return 6}`, RunOutput: int64(6), Output: map[string]interface{}{"a": int64(3)}}, + {Script: `a = 4; switch a {case 1: return 5; case 2,3: return 6}`, Output: map[string]interface{}{"a": int64(4)}}, + + // test 3 cases + {Script: `a = 1; switch a {case 1,2: return 5; case 3: return 6}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 2; switch a {case 1,2: return 5; case 3: return 6}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 3; switch a {case 1,2: return 5; case 3: return 6}`, RunOutput: int64(6), Output: map[string]interface{}{"a": int64(3)}}, + {Script: `a = 4; switch a {case 1,2: return 5; case 3: return 6}`, Output: map[string]interface{}{"a": int64(4)}}, + {Script: `a = 1; switch a {case 1,2: return 5; case 2,3: return 6}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 2; switch a {case 1,2: return 5; case 2,3: return 6}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 3; switch a {case 1,2: return 5; case 2,3: return 6}`, RunOutput: int64(6), Output: map[string]interface{}{"a": int64(3)}}, + {Script: `a = 4; switch a {case 1,2: return 5; case 2,3: return 6}`, Output: map[string]interface{}{"a": int64(4)}}, + + // test default + {Script: `a = 1; switch a {default: return 5}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; switch a {case 1: return 5; case 2: return 6; default: return 7}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 2; switch a {case 1: return 5; case 2: return 6; default: return 7}`, RunOutput: int64(6), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 3; switch a {case 1: return 5; case 2: return 6; default: return 7}`, RunOutput: int64(7), Output: map[string]interface{}{"a": int64(3)}}, + {Script: `a = 1; switch a {case 1: return 5; case 2,3: return 6; default: return 7}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 2; switch a {case 1: return 5; case 2,3: return 6; default: return 7}`, RunOutput: int64(6), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 3; switch a {case 1: return 5; case 2,3: return 6; default: return 7}`, RunOutput: int64(6), Output: map[string]interface{}{"a": int64(3)}}, + {Script: `a = 4; switch a {case 1: return 5; case 2,3: return 6; default: return 7}`, RunOutput: int64(7), Output: map[string]interface{}{"a": int64(4)}}, + {Script: `a = 1; switch a {case 1,2: return 5; case 3: return 6; default: return 7}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 2; switch a {case 1,2: return 5; case 3: return 6; default: return 7}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 3; switch a {case 1,2: return 5; case 3: return 6; default: return 7}`, RunOutput: int64(6), Output: map[string]interface{}{"a": int64(3)}}, + {Script: `a = 4; switch a {case 1,2: return 5; case 3: return 6; default: return 7}`, RunOutput: int64(7), Output: map[string]interface{}{"a": int64(4)}}, + + // test scope + {Script: `a = 1; switch a {case 1: a = 5}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(5)}}, + {Script: `a = 2; switch a {case 1: a = 5}`, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 1; b = 5; switch a {case 1: b = 6}`, RunOutput: int64(6), Output: map[string]interface{}{"a": int64(1), "b": int64(6)}}, + {Script: `a = 2; b = 5; switch a {case 1: b = 6}`, Output: map[string]interface{}{"a": int64(2), "b": int64(5)}}, + {Script: `a = 1; b = 5; switch a {case 1: b = 6; default: b = 7}`, RunOutput: int64(6), Output: map[string]interface{}{"a": int64(1), "b": int64(6)}}, + {Script: `a = 2; b = 5; switch a {case 1: b = 6; default: b = 7}`, RunOutput: int64(7), Output: map[string]interface{}{"a": int64(2), "b": int64(7)}}, + + // test scope without define b + {Script: `a = 1; switch a {case 1: b = 5}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 2; switch a {case 1: b = 5}`, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 1; switch a {case 1: b = 5}; b`, RunError: fmt.Errorf("undefined symbol 'b'"), RunOutput: nil, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 2; switch a {case 1: b = 5}; b`, RunError: fmt.Errorf("undefined symbol 'b'"), RunOutput: nil, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 1; switch a {case 1: b = 5; default: b = 6}`, RunOutput: int64(5), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 2; switch a {case 1: b = 5; default: b = 6}`, RunOutput: int64(6), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 1; switch a {case 1: b = 5; default: b = 6}; b`, RunError: fmt.Errorf("undefined symbol 'b'"), RunOutput: nil, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 2; switch a {case 1: b = 5; default: b = 6}; b`, RunError: fmt.Errorf("undefined symbol 'b'"), RunOutput: nil, Output: map[string]interface{}{"a": int64(2)}}, + + // test new lines + {Script: ` +a = 1; +switch a { + case 1: + return 1 +}`, RunOutput: int64(1)}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestForLoop(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `for in [1] { }`, ParseError: fmt.Errorf("missing identifier")}, + {Script: `for a, b, c in [1] { }`, ParseError: fmt.Errorf("too many identifiers")}, + + {Script: `break`, RunError: fmt.Errorf("unexpected break statement")}, + {Script: `continue`, RunError: fmt.Errorf("unexpected continue statement")}, + {Script: `for 1++ { }`, RunError: fmt.Errorf("invalid operation")}, + {Script: `for { 1++ }`, RunError: fmt.Errorf("invalid operation")}, + {Script: `for a in 1++ { }`, RunError: fmt.Errorf("invalid operation")}, + + {Script: `for { break }`, RunOutput: nil}, + {Script: `for {a = 1; if a == 1 { break } }`, RunOutput: nil}, + {Script: `a = 1; for { if a == 1 { break } }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; for { if a == 1 { break }; a++ }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; for { if a == 3 { break }; a++ }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(3)}}, + + {Script: `a = 1; for { if a == 1 { return }; a++ }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; for { if a == 3 { return }; a++ }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(3)}}, + {Script: `a = 1; for { if a == 1 { return 2 }; a++ }`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; for { if a == 3 { return 2 }; a++ }`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(3)}}, + + {Script: `a = 1; for { if a == 3 { return 3 }; a++ }; return 2`, RunOutput: int64(3), Output: map[string]interface{}{"a": int64(3)}}, + + {Script: `a = 1; for { a++; if a == 2 { continue } else { break } }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(3)}}, + {Script: `a = 1; for { a++; if a == 2 { continue }; if a == 3 { break } }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(3)}}, + + {Script: `for a in [1] { if a == 1 { break } }`, RunOutput: nil}, + {Script: `for a in [1, 2] { if a == 2 { break } }`, RunOutput: nil}, + {Script: `for a in [1, 2, 3] { if a == 3 { break } }`, RunOutput: nil}, + + {Script: `for a in [1, 2, 3] { if a == 2 { return 2 } }; return 3`, RunOutput: int64(2)}, + + {Script: `a = [1]; for b in a { if b == 1 { break } }`, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{int64(1)}}}, + {Script: `a = [1, 2]; for b in a { if b == 2 { break } }`, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2)}}}, + {Script: `a = [1, 2, 3]; for b in a { if b == 3 { break } }`, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}}, + + {Script: `a = [1]; b = 0; for c in a { b = c }`, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{int64(1)}, "b": int64(1)}}, + {Script: `a = [1, 2]; b = 0; for c in a { b = c }`, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2)}, "b": int64(2)}}, + {Script: `a = [1, 2, 3]; b = 0; for c in a { b = c }`, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}, "b": int64(3)}}, + + {Script: `a = 1; for a < 2 { a++ }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 1; for a < 3 { a++ }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(3)}}, + + {Script: `a = 1; for nil { a++; if a > 2 { break } }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; for nil { a++; if a > 3 { break } }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; for true { a++; if a > 2 { break } }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(3)}}, + {Script: `a = 1; for true { a++; if a > 3 { break } }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(4)}}, + + {Script: `func x() { return [1] }; for b in x() { if b == 1 { break } }`, RunOutput: nil}, + {Script: `func x() { return [1, 2] }; for b in x() { if b == 2 { break } }`, RunOutput: nil}, + {Script: `func x() { return [1, 2, 3] }; for b in x() { if b == 3 { break } }`, RunOutput: nil}, + + {Script: `func x() { a = 1; for { if a == 1 { return } } }; x()`, RunOutput: nil}, + {Script: `func x() { a = 1; for { if a == 1 { return nil } } }; x()`, RunOutput: nil}, + {Script: `func x() { a = 1; for { if a == 1 { return true } } }; x()`, RunOutput: true}, + {Script: `func x() { a = 1; for { if a == 1 { return 1 } } }; x()`, RunOutput: int64(1)}, + {Script: `func x() { a = 1; for { if a == 1 { return 1.1 } } }; x()`, RunOutput: float64(1.1)}, + {Script: `func x() { a = 1; for { if a == 1 { return "a" } } }; x()`, RunOutput: "a"}, + + {Script: `func x() { for a in [1, 2, 3] { if a == 3 { return } } }; x()`, RunOutput: nil}, + {Script: `func x() { for a in [1, 2, 3] { if a == 3 { return 3 } }; return 2 }; x()`, RunOutput: int64(3)}, + {Script: `func x() { for a in [1, 2, 3] { if a == 1 { continue } } }; x()`, RunOutput: nil}, + {Script: `func x() { for a in [1, 2, 3] { if a == 1 { continue }; if a == 3 { return } } }; x()`, RunOutput: nil}, + + {Script: `func x() { return [1, 2] }; a = 1; for i in x() { a++ }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(3)}}, + {Script: `func x() { return [1, 2, 3] }; a = 1; for i in x() { a++ }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(4)}}, + + {Script: `for a = 1; nil; nil { return }`}, + // TOFIX: + // {Script: `for a, b = 1; nil; nil { return }`}, + // {Script: `for a, b = 1, 2; nil; nil { return }`}, + + {Script: `var a = 1; for ; ; { return a }`, RunOutput: int64(1)}, + {Script: `var a = 1; for ; ; a++ { return a }`, RunOutput: int64(1)}, + {Script: `var a = 1; for ; a > 0 ; { return a }`, RunOutput: int64(1)}, + {Script: `var a = 1; for ; a > 0 ; a++ { return a }`, RunOutput: int64(1)}, + {Script: `for var a = 1 ; ; { return a }`, RunOutput: int64(1)}, + {Script: `for var a = 1 ; ; a++ { return a }`, RunOutput: int64(1)}, + {Script: `for var a = 1 ; a > 0 ; { return a }`, RunOutput: int64(1)}, + {Script: `for var a = 1 ; a > 0 ; a++ { return a }`, RunOutput: int64(1)}, + + {Script: `for var a = 1; nil; nil { return }`}, + {Script: `for var a = 1, 2; nil; nil { return }`}, + {Script: `for var a, b = 1; nil; nil { return }`}, + {Script: `for var a, b = 1, 2; nil; nil { return }`}, + + {Script: `for a.b = 1; nil; nil { return }`, RunError: fmt.Errorf("undefined symbol 'a'")}, + + {Script: `for a = 1; nil; nil { if a == 1 { break } }`, RunOutput: nil}, + {Script: `for a = 1; nil; nil { if a == 2 { break }; a++ }`, RunOutput: nil}, + {Script: `for a = 1; nil; nil { a++; if a == 3 { break } }`, RunOutput: nil}, + + {Script: `for a = 1; a < 1; nil { }`, RunOutput: nil}, + {Script: `for a = 1; a > 1; nil { }`, RunOutput: nil}, + {Script: `for a = 1; a == 1; nil { break }`, RunOutput: nil}, + + {Script: `for a = 1; a == 1; a++ { }`, RunOutput: nil}, + {Script: `for a = 1; a < 2; a++ { }`, RunOutput: nil}, + {Script: `for a = 1; a < 3; a++ { }`, RunOutput: nil}, + + {Script: `for a = 1; a < 5; a++ { if a == 3 { return 3 } }; return 2`, RunOutput: int64(3)}, + + {Script: `a = 1; for b = 1; a < 1; a++ { }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; for b = 1; a < 2; a++ { }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 1; for b = 1; a < 3; a++ { }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(3)}}, + + {Script: `a = 1; for b = 1; a < 1; a++ { if a == 1 { continue } }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; for b = 1; a < 2; a++ { if a == 1 { continue } }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 1; for b = 1; a < 3; a++ { if a == 1 { continue } }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(3)}}, + + {Script: `a = 1; for b = 1; a < 1; a++ { if a == 1 { break } }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; for b = 1; a < 2; a++ { if a == 1 { break } }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; for b = 1; a < 3; a++ { if a == 1 { break } }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; for b = 1; a < 1; a++ { if a == 2 { break } }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; for b = 1; a < 2; a++ { if a == 2 { break } }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 1; for b = 1; a < 3; a++ { if a == 2 { break } }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 1; for b = 1; a < 1; a++ { if a == 3 { break } }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; for b = 1; a < 2; a++ { if a == 3 { break } }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 1; for b = 1; a < 3; a++ { if a == 3 { break } }`, RunOutput: nil, Output: map[string]interface{}{"a": int64(3)}}, + + {Script: `a = ["123", "456", "789"]; b = ""; for i = 0; i < len(a); i++ { b += a[i][len(a[i]) - 2:]; b += a[i][:len(a[i]) - 2] }`, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{"123", "456", "789"}, "b": "231564897"}}, + {Script: `a = [[["123"], ["456"]], [["789"]]]; b = ""; for i = 0; i < len(a); i++ { for j = 0; j < len(a[i]); j++ { for k = 0; k < len(a[i][j]); k++ { for l = 0; l < len(a[i][j][k]); l++ { b += a[i][j][k][l] + "-" } } } }`, + RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{[]interface{}{[]interface{}{"123"}, []interface{}{"456"}}, []interface{}{[]interface{}{"789"}}}, "b": "1-2-3-4-5-6-7-8-9-"}}, + + {Script: `func x() { for a = 1; a < 3; a++ { if a == 1 { return a } } }; x()`, RunOutput: int64(1)}, + {Script: `func x() { for a = 1; a < 3; a++ { if a == 2 { return a } } }; x()`, RunOutput: int64(2)}, + {Script: `func x() { for a = 1; a < 3; a++ { if a == 3 { return a } } }; x()`, RunOutput: nil}, + {Script: `func x() { for a = 1; a < 3; a++ { if a == 4 { return a } } }; x()`, RunOutput: nil}, + + {Script: `func x() { a = 1; for b = 1; a < 3; a++ { if a == 1 { continue } }; return a }; x()`, RunOutput: int64(3)}, + {Script: `func x() { a = 1; for b = 1; a < 3; a++ { if a == 2 { continue } }; return a }; x()`, RunOutput: int64(3)}, + {Script: `func x() { a = 1; for b = 1; a < 3; a++ { if a == 3 { continue } }; return a }; x()`, RunOutput: int64(3)}, + {Script: `func x() { a = 1; for b = 1; a < 3; a++ { if a == 4 { continue } }; return a }; x()`, RunOutput: int64(3)}, + + {Script: `b = 0; for i in a { b = i }`, Input: map[string]interface{}{"a": []interface{}{reflect.Value{}}}, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{reflect.Value{}}, "b": reflect.Value{}}}, + {Script: `b = 0; for i in a { b = i }`, Input: map[string]interface{}{"a": []interface{}{nil}}, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{nil}, "b": nil}}, + {Script: `b = 0; for i in a { b = i }`, Input: map[string]interface{}{"a": []interface{}{true}}, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{true}, "b": true}}, + {Script: `b = 0; for i in a { b = i }`, Input: map[string]interface{}{"a": []interface{}{int32(1)}}, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{int32(1)}, "b": int32(1)}}, + {Script: `b = 0; for i in a { b = i }`, Input: map[string]interface{}{"a": []interface{}{int64(1)}}, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{int64(1)}, "b": int64(1)}}, + {Script: `b = 0; for i in a { b = i }`, Input: map[string]interface{}{"a": []interface{}{float32(1.1)}}, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{float32(1.1)}, "b": float32(1.1)}}, + {Script: `b = 0; for i in a { b = i }`, Input: map[string]interface{}{"a": []interface{}{float64(1.1)}}, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{float64(1.1)}, "b": float64(1.1)}}, + + {Script: `b = 0; for i in a { b = i }`, Input: map[string]interface{}{"a": []interface{}{interface{}(reflect.Value{})}}, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{interface{}(reflect.Value{})}, "b": interface{}(reflect.Value{})}}, + {Script: `b = 0; for i in a { b = i }`, Input: map[string]interface{}{"a": []interface{}{interface{}(nil)}}, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{interface{}(nil)}, "b": interface{}(nil)}}, + {Script: `b = 0; for i in a { b = i }`, Input: map[string]interface{}{"a": []interface{}{interface{}(true)}}, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{interface{}(true)}, "b": interface{}(true)}}, + {Script: `b = 0; for i in a { b = i }`, Input: map[string]interface{}{"a": []interface{}{interface{}(int32(1))}}, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{interface{}(int32(1))}, "b": interface{}(int32(1))}}, + {Script: `b = 0; for i in a { b = i }`, Input: map[string]interface{}{"a": []interface{}{interface{}(int64(1))}}, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{interface{}(int64(1))}, "b": interface{}(int64(1))}}, + {Script: `b = 0; for i in a { b = i }`, Input: map[string]interface{}{"a": []interface{}{interface{}(float32(1.1))}}, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{interface{}(float32(1.1))}, "b": interface{}(float32(1.1))}}, + {Script: `b = 0; for i in a { b = i }`, Input: map[string]interface{}{"a": []interface{}{interface{}(float64(1.1))}}, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{interface{}(float64(1.1))}, "b": interface{}(float64(1.1))}}, + + {Script: `b = 0; for i in a { b = i }`, Input: map[string]interface{}{"a": interface{}([]interface{}{nil})}, RunOutput: nil, Output: map[string]interface{}{"a": interface{}([]interface{}{nil}), "b": nil}}, + + {Script: `for i in nil { }`, RunError: fmt.Errorf("for cannot loop over type interface")}, + {Script: `for i in true { }`, RunError: fmt.Errorf("for cannot loop over type bool")}, + {Script: `for i in a { }`, Input: map[string]interface{}{"a": reflect.Value{}}, RunError: fmt.Errorf("for cannot loop over type struct"), Output: map[string]interface{}{"a": reflect.Value{}}}, + {Script: `for i in a { }`, Input: map[string]interface{}{"a": interface{}(nil)}, RunError: fmt.Errorf("for cannot loop over type interface"), Output: map[string]interface{}{"a": interface{}(nil)}}, + {Script: `for i in a { }`, Input: map[string]interface{}{"a": interface{}(true)}, RunError: fmt.Errorf("for cannot loop over type bool"), Output: map[string]interface{}{"a": interface{}(true)}}, + {Script: `for i in [1, 2, 3] { b++ }`, RunError: fmt.Errorf("undefined symbol 'b'")}, + {Script: `for a = 1; a < 3; a++ { b++ }`, RunError: fmt.Errorf("undefined symbol 'b'")}, + {Script: `for a = b; a < 3; a++ { }`, RunError: fmt.Errorf("undefined symbol 'b'")}, + {Script: `for a = 1; b < 3; a++ { }`, RunError: fmt.Errorf("undefined symbol 'b'")}, + {Script: `for a = 1; a < 3; b++ { }`, RunError: fmt.Errorf("undefined symbol 'b'")}, + + {Script: `a = 1; b = [{"c": "c"}]; for i in b { a = i }`, RunOutput: nil, Output: map[string]interface{}{"a": map[interface{}]interface{}{"c": "c"}, "b": []interface{}{map[interface{}]interface{}{"c": "c"}}}}, + {Script: `a = 1; b = {"x": [{"y": "y"}]}; for i in b.x { a = i }`, RunOutput: nil, Output: map[string]interface{}{"a": map[interface{}]interface{}{"y": "y"}, "b": map[interface{}]interface{}{"x": []interface{}{map[interface{}]interface{}{"y": "y"}}}}}, + + {Script: `a = {}; b = 1; for i in a { b = i }; b`, RunOutput: int64(1), Output: map[string]interface{}{"a": map[interface{}]interface{}{}, "b": int64(1)}}, + {Script: `a = {"x": 2}; b = 1; for i in a { b = i }; b`, RunOutput: "x", Output: map[string]interface{}{"a": map[interface{}]interface{}{"x": int64(2)}, "b": "x"}}, + {Script: `a = {"x": 2, "y": 3}; b = 0; for i in a { b++ }; b`, RunOutput: int64(2), Output: map[string]interface{}{"a": map[interface{}]interface{}{"x": int64(2), "y": int64(3)}, "b": int64(2)}}, + {Script: `a = {"x": 2, "y": 3}; for i in a { b++ }`, RunError: fmt.Errorf("undefined symbol 'b'"), Output: map[string]interface{}{"a": map[interface{}]interface{}{"x": int64(2), "y": int64(3)}}}, + + {Script: `a = {"x": 2, "y": 3}; b = 0; for i in a { if i == "x" { continue }; b = i }; b`, RunOutput: "y", Output: map[string]interface{}{"a": map[interface{}]interface{}{"x": int64(2), "y": int64(3)}, "b": "y"}}, + {Script: `a = {"x": 2, "y": 3}; b = 0; for i in a { if i == "y" { continue }; b = i }; b`, RunOutput: "x", Output: map[string]interface{}{"a": map[interface{}]interface{}{"x": int64(2), "y": int64(3)}, "b": "x"}}, + {Script: `a = {"x": 2, "y": 3}; for i in a { if i == "x" { return 1 } }`, RunOutput: int64(1), Output: map[string]interface{}{"a": map[interface{}]interface{}{"x": int64(2), "y": int64(3)}}}, + {Script: `a = {"x": 2, "y": 3}; for i in a { if i == "y" { return 2 } }`, RunOutput: int64(2), Output: map[string]interface{}{"a": map[interface{}]interface{}{"x": int64(2), "y": int64(3)}}}, + {Script: `a = {"x": 2, "y": 3}; b = 0; for i in a { if i == "x" { break }; b++ }; if b > 1 { return false } else { return true }`, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]interface{}{"x": int64(2), "y": int64(3)}}}, + {Script: `a = {"x": 2, "y": 3}; b = 0; for i in a { if i == "y" { break }; b++ }; if b > 1 { return false } else { return true }`, RunOutput: true, Output: map[string]interface{}{"a": map[interface{}]interface{}{"x": int64(2), "y": int64(3)}}}, + {Script: `a = {"x": 2, "y": 3}; b = 1; for i in a { if (i == "x" || i == "y") { break }; b++ }; b`, RunOutput: int64(1), Output: map[string]interface{}{"a": map[interface{}]interface{}{"x": int64(2), "y": int64(3)}, "b": int64(1)}}, + + {Script: `a = ["123", "456", "789"]; b = ""; for v in a { b += v[len(v) - 2:]; b += v[:len(v) - 2] }`, RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{"123", "456", "789"}, "b": "231564897"}}, + {Script: `a = [[["123"], ["456"]], [["789"]]]; b = ""; for x in a { for y in x { for z in y { for i = 0; i < len(z); i++ { b += z[i] + "-" } } } }`, + RunOutput: nil, Output: map[string]interface{}{"a": []interface{}{[]interface{}{[]interface{}{"123"}, []interface{}{"456"}}, []interface{}{[]interface{}{"789"}}}, "b": "1-2-3-4-5-6-7-8-9-"}}, + + {Script: `a = {"x": 2}; b = 0; for k, v in a { b = k }; b`, RunOutput: "x", Output: map[string]interface{}{"a": map[interface{}]interface{}{"x": int64(2)}, "b": "x"}}, + {Script: `a = {"x": 2}; b = 0; for k, v in a { b = v }; b`, RunOutput: int64(2), Output: map[string]interface{}{"a": map[interface{}]interface{}{"x": int64(2)}, "b": int64(2)}}, + + {Script: `a = make(chan int64, 2); a <- 1; v = 0; for val in a { v = val; break; }; v`, RunOutput: int64(1), Output: map[string]interface{}{"v": int64(1)}}, + {Script: `a = make(chan int64, 4); a <- 1; a <- 2; a <- 3; for i in a { if i == 2 { return 2 } }; return 4`, RunOutput: int64(2)}, + {Script: `a = make(chan int64, 2); a <- 1; for i in a { if i < 4 { a <- i + 1; continue }; return 4 }; return 6`, RunOutput: int64(4)}, + + // test non-buffer and go func + {Script: `a = make(chan int64); go func() { a <- 1; a <- 2; a <- 3 }(); b = []; for i in a { b += i; if i > 2 { break } }`, RunOutput: nil, Output: map[string]interface{}{"b": []interface{}{int64(1), int64(2), int64(3)}}}, + {Script: `a = make(chan int64); go func() { a <- 1; a <- 2; a <- 3; close(a) }(); b = []; for i in a { b += i }`, RunOutput: nil, Output: map[string]interface{}{"b": []interface{}{int64(1), int64(2), int64(3)}}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestItemInList(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `"a" in ["a"]`, RunOutput: true}, + {Script: `"a" in ["b"]`, RunOutput: false}, + {Script: `"a" in ["c", "b", "a"]`, RunOutput: true}, + {Script: `"a" in ["a", "b", 1]`, RunOutput: true}, + {Script: `"a" in l`, Input: map[string]interface{}{"l": []interface{}{"a"}}, RunOutput: true}, + {Script: `"a" in l`, Input: map[string]interface{}{"l": []interface{}{"b"}}, RunOutput: false}, + {Script: `"a" in l`, Input: map[string]interface{}{"l": []interface{}{"c", "b", "a"}}, RunOutput: true}, + {Script: `"a" in l`, Input: map[string]interface{}{"l": []interface{}{"a", "b", 1}}, RunOutput: true}, + + {Script: `1 in [1]`, RunOutput: true}, + {Script: `1 in [2]`, RunOutput: false}, + {Script: `1 in [3, 2, 1]`, RunOutput: true}, + {Script: `1 in ["1"]`, RunOutput: true}, + {Script: `1 in l`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: true}, + {Script: `"1" in l`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: true}, + {Script: `1 in l`, Input: map[string]interface{}{"l": []interface{}{2}}, RunOutput: false}, + {Script: `1 in l`, Input: map[string]interface{}{"l": []interface{}{3, 2, 1}}, RunOutput: true}, + {Script: `1 in l`, Input: map[string]interface{}{"l": []interface{}{"1"}}, RunOutput: true}, + + {Script: `0.9 in [1]`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: false}, + {Script: `1.0 in [1]`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: true}, + {Script: `1.1 in [1]`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: false}, + {Script: `1 in [0.9]`, Input: map[string]interface{}{"l": []interface{}{0.9}}, RunOutput: false}, + {Script: `1 in [1.0]`, Input: map[string]interface{}{"l": []interface{}{1.0}}, RunOutput: true}, + {Script: `1 in [1.1]`, Input: map[string]interface{}{"l": []interface{}{1.1}}, RunOutput: false}, + {Script: `0.9 in [1]`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: false}, + {Script: `1.0 in [1]`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: true}, + {Script: `1.1 in [1]`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: false}, + {Script: `1 in [0.9]`, Input: map[string]interface{}{"l": []interface{}{0.9}}, RunOutput: false}, + {Script: `1 in [1.0]`, Input: map[string]interface{}{"l": []interface{}{1.0}}, RunOutput: true}, + {Script: `1 in [1.1]`, Input: map[string]interface{}{"l": []interface{}{1.1}}, RunOutput: false}, + + {Script: `true in ["true"]`, RunOutput: true}, + {Script: `true in [true]`, RunOutput: true}, + {Script: `true in [true, false]`, RunOutput: true}, + {Script: `false in [false, true]`, RunOutput: true}, + {Script: `true in l`, Input: map[string]interface{}{"l": []interface{}{"true"}}, RunOutput: true}, + {Script: `true in l`, Input: map[string]interface{}{"l": []interface{}{true}}, RunOutput: true}, + {Script: `true in l`, Input: map[string]interface{}{"l": []interface{}{true, false}}, RunOutput: true}, + {Script: `false in l`, Input: map[string]interface{}{"l": []interface{}{false, true}}, RunOutput: true}, + + {Script: `"a" in ["b", "a", "c"][1:]`, RunOutput: true}, + {Script: `"a" in ["b", "a", "c"][:1]`, RunOutput: false}, + {Script: `"a" in ["b", "a", "c"][1:2]`, RunOutput: true}, + {Script: `l = ["b", "a", "c"];"a" in l[1:]`, RunOutput: true}, + {Script: `l = ["b", "a", "c"];"a" in l[:1]`, RunOutput: false}, + {Script: `l = ["b", "a", "c"];"a" in l[1:2]`, RunOutput: true}, + {Script: `"a" in l[1:]`, Input: map[string]interface{}{"l": []interface{}{"b", "a", "c"}}, RunOutput: true}, + {Script: `"a" in l[:1]`, Input: map[string]interface{}{"l": []interface{}{"b", "a", "c"}}, RunOutput: false}, + {Script: `"a" in l[1:2]`, Input: map[string]interface{}{"l": []interface{}{"b", "a", "c"}}, RunOutput: true}, + + // for i in list && item in list + {Script: `list_of_list = [["a"]];for l in list_of_list { return "a" in l }`, RunOutput: true}, + {Script: `for l in list_of_list { return "a" in l }`, Input: map[string]interface{}{"list_of_list": []interface{}{[]interface{}{"a"}}}, RunOutput: true}, + + // not slice or array + // todo: support `"a" in "aaa"` ? + {Script: `"a" in "aaa"`, RunError: fmt.Errorf("second argument must be slice or array; but have string")}, + {Script: `1 in 12345`, RunError: fmt.Errorf("second argument must be slice or array; but have int64")}, + + // a in item in list + {Script: `"a" in 5 in [1, 2, 3]`, RunError: fmt.Errorf("second argument must be slice or array; but have bool")}, + + // applying a in b in several part of expresstion/statement + {Script: `switch 1 in [1] {case true: return true;default: return false}`, RunOutput: true}, + {Script: `switch 1 in [2,3] {case true: return true;default: return false}`, RunOutput: false}, + {Script: `switch true {case 1 in [1]: return true;default: return false}`, RunOutput: true}, + {Script: `switch false {case 1 in [1]: return true;default: return false}`, RunOutput: false}, + {Script: `if 1 in [1] {return true} else {return false}`, RunOutput: true}, + {Script: `if 1 in [2,3] {return true} else {return false}`, RunOutput: false}, + {Script: `for i in [1,2,3] { i++ }`}, + {Script: `a=1; a=a in [1]`, RunOutput: true}, + {Script: `a=1; a=a in [2,3]`, RunOutput: false}, + {Script: `1 in [1] && true`, RunOutput: true}, + {Script: `1 in [1] && false`, RunOutput: false}, + {Script: `1 in [1] || true`, RunOutput: true}, + {Script: `1 in [1] || false`, RunOutput: true}, + {Script: `1 in [2,3] && true`, RunOutput: false}, + {Script: `1 in [2,3] && false`, RunOutput: false}, + {Script: `1 in [2,3] || true`, RunOutput: true}, + {Script: `1 in [2,3] || false`, RunOutput: false}, + {Script: `1++ in [1, 2, 3]`, RunError: fmt.Errorf("invalid operation")}, + {Script: `3++ in [1, 2, 3]`, RunError: fmt.Errorf("invalid operation")}, + {Script: `1 in 1++`, RunError: fmt.Errorf("invalid operation")}, + {Script: `a=1;a++ in [1, 2, 3]`, RunOutput: true}, + {Script: `a=3;a++ in [1, 2, 3]`, RunOutput: false}, + {Script: `switch 1 in l {case true: return true;default: return false}`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: true}, + {Script: `switch 1 in l {case true: return true;default: return false}`, Input: map[string]interface{}{"l": []interface{}{2, 3}}, RunOutput: false}, + {Script: `switch true {case 1 in l: return true;default: return false}`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: true}, + {Script: `switch false {case 1 in l: return true;default: return false}`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: false}, + {Script: `if 1 in l {return true} else {return false}`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: true}, + {Script: `if 1 in l {return true} else {return false}`, Input: map[string]interface{}{"l": []interface{}{2, 3}}, RunOutput: false}, + {Script: `for i in l { i++ }`, Input: map[string]interface{}{"l": []interface{}{1, 2, 3}}}, + {Script: `a=1; a=a in l`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: true}, + {Script: `a=1; a=a in l`, Input: map[string]interface{}{"l": []interface{}{2, 3}}, RunOutput: false}, + {Script: `1 in l && true`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: true}, + {Script: `1 in l && false`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: false}, + {Script: `1 in l || true`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: true}, + {Script: `1 in l || false`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: true}, + {Script: `1 in l && true`, Input: map[string]interface{}{"l": []interface{}{2, 3}}, RunOutput: false}, + {Script: `1 in l && false`, Input: map[string]interface{}{"l": []interface{}{2, 3}}, RunOutput: false}, + {Script: `1 in l || true`, Input: map[string]interface{}{"l": []interface{}{2, 3}}, RunOutput: true}, + {Script: `1 in l || false`, Input: map[string]interface{}{"l": []interface{}{2, 3}}, RunOutput: false}, + {Script: `1++ in l`, Input: map[string]interface{}{"l": []interface{}{1, 2, 3}}, RunError: fmt.Errorf("invalid operation")}, + {Script: `3++ in l`, Input: map[string]interface{}{"l": []interface{}{1, 2, 3}}, RunError: fmt.Errorf("invalid operation")}, + {Script: `a=1;a++ in l`, Input: map[string]interface{}{"l": []interface{}{1, 2, 3}}, RunOutput: true}, + {Script: `a=3;a++ in l`, Input: map[string]interface{}{"l": []interface{}{1, 2, 3}}, RunOutput: false}, + + // multidimensional slice + {Script: `1 in [1]`, RunOutput: true}, + {Script: `1 in [[1]]`, RunOutput: false}, + {Script: `1 in [[[1]]]`, RunOutput: false}, + {Script: `1 in [[1],[[1]],1]`, RunOutput: true}, + {Script: `[1] in [1]`, RunOutput: false}, + {Script: `[1] in [[1]]`, RunOutput: true}, + {Script: `[1] in [[[1]]]`, RunOutput: false}, + {Script: `[[1]] in [1]`, RunOutput: false}, + {Script: `[[1]] in [[1]]`, RunOutput: false}, + {Script: `[[1]] in [[[1]]]`, RunOutput: true}, + {Script: `1 in [1]`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: true}, + {Script: `1 in [[1]]`, Input: map[string]interface{}{"l": []interface{}{[]interface{}{1}}}, RunOutput: false}, + {Script: `1 in [[[1]]]`, Input: map[string]interface{}{"l": []interface{}{[]interface{}{[]interface{}{1}}}}, RunOutput: false}, + {Script: `1 in [[1],[[1]],1]`, Input: map[string]interface{}{"l": []interface{}{[]interface{}{1}, []interface{}{[]interface{}{1}}, 1}}, RunOutput: true}, + {Script: `[1] in [1]`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: false}, + {Script: `[1] in [[1]]`, Input: map[string]interface{}{"l": []interface{}{[]interface{}{1}}}, RunOutput: true}, + {Script: `[1] in [[[1]]]`, Input: map[string]interface{}{"l": []interface{}{[]interface{}{[]interface{}{1}}}}, RunOutput: false}, + {Script: `[[1]] in [1]`, Input: map[string]interface{}{"l": []interface{}{1}}, RunOutput: false}, + {Script: `[[1]] in [[1]]`, Input: map[string]interface{}{"l": []interface{}{[]interface{}{1}}}, RunOutput: false}, + {Script: `[[1]] in [[[1]]]`, Input: map[string]interface{}{"l": []interface{}{[]interface{}{[]interface{}{1}}}}, RunOutput: true}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestOperatorPrecedence(t *testing.T) { + t.Parallel() + + tests := []Test{ + // test && > || + {Script: `true || true && false`, RunOutput: true}, + {Script: `(true || true) && false`, RunOutput: false}, + {Script: `false && true || true`, RunOutput: true}, + {Script: `false && (true || true)`, RunOutput: false}, + + // test == > || + {Script: `0 == 1 || 1 == 1`, RunOutput: true}, + {Script: `0 == (1 || 1) == 1`, RunOutput: false}, + + // test + > == + {Script: `1 + 2 == 2 + 1`, RunOutput: true}, + {Script: `1 + (2 == 2) + 1`, RunOutput: int64(3)}, + + // test * > + + {Script: `2 * 3 + 4 * 5`, RunOutput: int64(26)}, + {Script: `2 * (3 + 4) * 5`, RunOutput: int64(70)}, + + // test * > && + {Script: `2 * 0 && 3 * 4`, RunOutput: false}, + {Script: `2 * (0 && 3) * 4`, RunOutput: int64(0)}, + + // test ++ > * + {Script: `a = 1; b = 2; a++ * b++`, RunOutput: int64(6)}, + + // test ++ > * + {Script: `a = 1; b = 2; a++ * b++`, RunOutput: int64(6)}, + + // test unary - > + + {Script: `a = 1; b = 2; -a + b`, RunOutput: int64(1)}, + {Script: `a = 1; b = 2; -(a + b)`, RunOutput: int64(-3)}, + {Script: `a = 1; b = 2; a + -b`, RunOutput: int64(-1)}, + + // test ! > || + {Script: `!true || true`, RunOutput: true}, + {Script: `!(true || true)`, RunOutput: false}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestTry(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `try { 1++ } catch { 1++ }`, RunError: fmt.Errorf("invalid operation")}, + {Script: `try { 1++ } catch a { return a }`, RunOutput: fmt.Errorf("invalid operation")}, + {Script: `try { 1++ } catch a { a = 2 }; return a`, RunError: fmt.Errorf("undefined symbol 'a'")}, + + // test finally + {Script: `try { 1++ } catch { 1++ } finally { return 1 }`, RunError: fmt.Errorf("invalid operation")}, + {Script: `try { } catch { } finally { 1++ }`, RunError: fmt.Errorf("invalid operation")}, + {Script: `try { } catch { 1 } finally { 1++ }`, RunError: fmt.Errorf("invalid operation")}, + {Script: `try { 1++ } catch { } finally { 1++ }`, RunError: fmt.Errorf("invalid operation")}, + {Script: `try { 1++ } catch a { } finally { return a }`, RunOutput: fmt.Errorf("invalid operation")}, + {Script: `try { 1++ } catch a { } finally { a = 2 }; return a`, RunError: fmt.Errorf("undefined symbol 'a'")}, + + {Script: `try { } catch { }`, RunOutput: nil}, + {Script: `try { 1++ } catch { }`, RunOutput: nil}, + {Script: `try { } catch { 1++ }`, RunOutput: nil}, + {Script: `try { return 1 } catch { }`, RunOutput: int64(1)}, + {Script: `try { return 1 } catch { return 2 }`, RunOutput: int64(2)}, + {Script: `try { 1++ } catch { return 1 }`, RunOutput: int64(1)}, + + // test finally + {Script: `try { } catch { } finally { return 1 }`, RunOutput: int64(1)}, + {Script: `try { 1++ } catch { } finally { return 1 }`, RunOutput: int64(1)}, + {Script: `try { 1++ } catch { return 1 } finally { 1++ }`, RunOutput: int64(1)}, + + // test variable scope + {Script: `try { 1++ } catch a { if a.Error() == "invalid operation" { return 1 } else { return 2 } }`, RunOutput: int64(1)}, + {Script: `try { 1++ } catch a { } finally { if a.Error() == "invalid operation" { return 1 } else { return 2 } }`, RunOutput: int64(1)}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} diff --git a/src/tool/run/vm/vmStmt.go b/src/tool/run/vm/vmStmt.go new file mode 100644 index 0000000..a71b782 --- /dev/null +++ b/src/tool/run/vm/vmStmt.go @@ -0,0 +1,803 @@ +package vm + +import ( + "context" + "fmt" + "reflect" + + "github.com/surdeus/goblin/src/tool/run/ast" + "github.com/surdeus/goblin/src/tool/run/env" + "github.com/surdeus/goblin/src/tool/run/parser" +) + +// Execute parses script and executes in the specified environment. +func Execute(env *env.Env, options *Options, script string) (interface{}, error) { + stmt, err := parser.ParseSrc(script) + if err != nil { + return nilValue, err + } + + return RunContext(context.Background(), env, options, stmt) +} + +// ExecuteContext parses script and executes in the specified environment with context. +func ExecuteContext(ctx context.Context, env *env.Env, options *Options, script string) (interface{}, error) { + stmt, err := parser.ParseSrc(script) + if err != nil { + return nilValue, err + } + + return RunContext(ctx, env, options, stmt) +} + +// Run executes statement in the specified environment. +func Run(env *env.Env, options *Options, stmt ast.Stmt) (interface{}, error) { + return RunContext(context.Background(), env, options, stmt) +} + +// RunContext executes statement in the specified environment with context. +func RunContext(ctx context.Context, env *env.Env, options *Options, stmt ast.Stmt) (interface{}, error) { + runInfo := runInfoStruct{ctx: ctx, env: env, options: options, stmt: stmt, rv: nilValue} + if runInfo.options == nil { + runInfo.options = &Options{} + } + runInfo.runSingleStmt() + if runInfo.err == ErrReturn { + runInfo.err = nil + } + return runInfo.rv.Interface(), runInfo.err +} + +// runSingleStmt executes statement in the specified environment with context. +func (runInfo *runInfoStruct) runSingleStmt() { + select { + case <-runInfo.ctx.Done(): + runInfo.rv = nilValue + runInfo.err = ErrInterrupt + return + default: + } + + switch stmt := runInfo.stmt.(type) { + + // nil + case nil: + + // StmtsStmt + case *ast.StmtsStmt: + for _, stmt := range stmt.Stmts { + switch stmt.(type) { + case *ast.BreakStmt: + runInfo.err = ErrBreak + return + case *ast.ContinueStmt: + runInfo.err = ErrContinue + return + case *ast.ReturnStmt: + runInfo.stmt = stmt + runInfo.runSingleStmt() + if runInfo.err != nil { + return + } + runInfo.err = ErrReturn + return + default: + runInfo.stmt = stmt + runInfo.runSingleStmt() + if runInfo.err != nil { + return + } + } + } + + // ExprStmt + case *ast.ExprStmt: + runInfo.expr = stmt.Expr + runInfo.invokeExpr() + + // VarStmt + case *ast.VarStmt: + // get right side expression values + rvs := make([]reflect.Value, len(stmt.Exprs)) + var i int + for i, runInfo.expr = range stmt.Exprs { + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + if env, ok := runInfo.rv.Interface().(*env.Env); ok { + rvs[i] = reflect.ValueOf(env.DeepCopy()) + } else { + rvs[i] = runInfo.rv + } + } + + if len(rvs) == 1 && len(stmt.Names) > 1 { + // only one right side value but many left side names + value := rvs[0] + if value.Kind() == reflect.Interface && !value.IsNil() { + value = value.Elem() + } + if (value.Kind() == reflect.Slice || value.Kind() == reflect.Array) && value.Len() > 0 { + // value is slice/array, add each value to left side names + for i := 0; i < value.Len() && i < len(stmt.Names); i++ { + runInfo.env.DefineValue(stmt.Names[i], value.Index(i)) + } + // return last value of slice/array + runInfo.rv = value.Index(value.Len() - 1) + return + } + } + + // define all names with right side values + for i = 0; i < len(rvs) && i < len(stmt.Names); i++ { + runInfo.env.DefineValue(stmt.Names[i], rvs[i]) + } + + // return last right side value + runInfo.rv = rvs[len(rvs)-1] + + // LetsStmt + case *ast.LetsStmt: + // get right side expression values + rvs := make([]reflect.Value, len(stmt.RHSS)) + var i int + for i, runInfo.expr = range stmt.RHSS { + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + if env, ok := runInfo.rv.Interface().(*env.Env); ok { + rvs[i] = reflect.ValueOf(env.DeepCopy()) + } else { + rvs[i] = runInfo.rv + } + } + + if len(rvs) == 1 && len(stmt.LHSS) > 1 { + // only one right side value but many left side expressions + value := rvs[0] + if value.Kind() == reflect.Interface && !value.IsNil() { + value = value.Elem() + } + if (value.Kind() == reflect.Slice || value.Kind() == reflect.Array) && value.Len() > 0 { + // value is slice/array, add each value to left side expression + for i := 0; i < value.Len() && i < len(stmt.LHSS); i++ { + runInfo.rv = value.Index(i) + runInfo.expr = stmt.LHSS[i] + runInfo.invokeLetExpr() + if runInfo.err != nil { + return + } + } + // return last value of slice/array + runInfo.rv = value.Index(value.Len() - 1) + return + } + } + + // invoke all left side expressions with right side values + for i = 0; i < len(rvs) && i < len(stmt.LHSS); i++ { + value := rvs[i] + if value.Kind() == reflect.Interface && !value.IsNil() { + value = value.Elem() + } + runInfo.rv = value + runInfo.expr = stmt.LHSS[i] + runInfo.invokeLetExpr() + if runInfo.err != nil { + return + } + } + + // return last right side value + runInfo.rv = rvs[len(rvs)-1] + + // LetMapItemStmt + case *ast.LetMapItemStmt: + runInfo.expr = stmt.RHS + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + var rvs []reflect.Value + if isNil(runInfo.rv) { + rvs = []reflect.Value{nilValue, falseValue} + } else { + rvs = []reflect.Value{runInfo.rv, trueValue} + } + var i int + for i, runInfo.expr = range stmt.LHSS { + runInfo.rv = rvs[i] + if runInfo.rv.Kind() == reflect.Interface && !runInfo.rv.IsNil() { + runInfo.rv = runInfo.rv.Elem() + } + runInfo.invokeLetExpr() + if runInfo.err != nil { + return + } + } + runInfo.rv = rvs[0] + + // IfStmt + case *ast.IfStmt: + // if + runInfo.expr = stmt.If + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + + env := runInfo.env + + if toBool(runInfo.rv) { + // then + runInfo.rv = nilValue + runInfo.stmt = stmt.Then + runInfo.env = env.NewEnv() + runInfo.runSingleStmt() + runInfo.env = env + return + } + + for _, statement := range stmt.ElseIf { + elseIf := statement.(*ast.IfStmt) + + // else if - if + runInfo.env = env.NewEnv() + runInfo.expr = elseIf.If + runInfo.invokeExpr() + if runInfo.err != nil { + runInfo.env = env + return + } + + if !toBool(runInfo.rv) { + continue + } + + // else if - then + runInfo.rv = nilValue + runInfo.stmt = elseIf.Then + runInfo.env = env.NewEnv() + runInfo.runSingleStmt() + runInfo.env = env + return + } + + if stmt.Else != nil { + // else + runInfo.rv = nilValue + runInfo.stmt = stmt.Else + runInfo.env = env.NewEnv() + runInfo.runSingleStmt() + } + + runInfo.env = env + + // TryStmt + case *ast.TryStmt: + // only the try statement will ignore any error except ErrInterrupt + // all other parts will return the error + + env := runInfo.env + runInfo.env = env.NewEnv() + + runInfo.stmt = stmt.Try + runInfo.runSingleStmt() + + if runInfo.err != nil { + if runInfo.err == ErrInterrupt { + runInfo.env = env + return + } + + // Catch + runInfo.stmt = stmt.Catch + if stmt.Var != "" { + runInfo.env.DefineValue(stmt.Var, reflect.ValueOf(runInfo.err)) + } + runInfo.err = nil + runInfo.runSingleStmt() + if runInfo.err != nil { + runInfo.env = env + return + } + } + + if stmt.Finally != nil { + // Finally + runInfo.stmt = stmt.Finally + runInfo.runSingleStmt() + } + + runInfo.env = env + + // LoopStmt + case *ast.LoopStmt: + env := runInfo.env + runInfo.env = env.NewEnv() + + for { + select { + case <-runInfo.ctx.Done(): + runInfo.err = ErrInterrupt + runInfo.rv = nilValue + runInfo.env = env + return + default: + } + + if stmt.Expr != nil { + runInfo.expr = stmt.Expr + runInfo.invokeExpr() + if runInfo.err != nil { + break + } + if !toBool(runInfo.rv) { + break + } + } + + runInfo.stmt = stmt.Stmt + runInfo.runSingleStmt() + if runInfo.err != nil { + if runInfo.err == ErrContinue { + runInfo.err = nil + continue + } + if runInfo.err == ErrReturn { + runInfo.env = env + return + } + if runInfo.err == ErrBreak { + runInfo.err = nil + } + break + } + } + + runInfo.rv = nilValue + runInfo.env = env + + // ForStmt + case *ast.ForStmt: + runInfo.expr = stmt.Value + runInfo.invokeExpr() + value := runInfo.rv + if runInfo.err != nil { + return + } + if value.Kind() == reflect.Interface && !value.IsNil() { + value = value.Elem() + } + + env := runInfo.env + runInfo.env = env.NewEnv() + + switch value.Kind() { + case reflect.Slice, reflect.Array: + for i := 0; i < value.Len(); i++ { + select { + case <-runInfo.ctx.Done(): + runInfo.err = ErrInterrupt + runInfo.rv = nilValue + runInfo.env = env + return + default: + } + + iv := value.Index(i) + if iv.Kind() == reflect.Interface && !iv.IsNil() { + iv = iv.Elem() + } + if iv.Kind() == reflect.Ptr { + iv = iv.Elem() + } + runInfo.env.DefineValue(stmt.Vars[0], iv) + + runInfo.stmt = stmt.Stmt + runInfo.runSingleStmt() + if runInfo.err != nil { + if runInfo.err == ErrContinue { + runInfo.err = nil + continue + } + if runInfo.err == ErrReturn { + runInfo.env = env + return + } + if runInfo.err == ErrBreak { + runInfo.err = nil + } + break + } + } + runInfo.rv = nilValue + runInfo.env = env + + case reflect.Map: + keys := value.MapKeys() + for i := 0; i < len(keys); i++ { + select { + case <-runInfo.ctx.Done(): + runInfo.err = ErrInterrupt + runInfo.rv = nilValue + runInfo.env = env + return + default: + } + + runInfo.env.DefineValue(stmt.Vars[0], keys[i]) + + if len(stmt.Vars) > 1 { + runInfo.env.DefineValue(stmt.Vars[1], value.MapIndex(keys[i])) + } + + runInfo.stmt = stmt.Stmt + runInfo.runSingleStmt() + if runInfo.err != nil { + if runInfo.err == ErrContinue { + runInfo.err = nil + continue + } + if runInfo.err == ErrReturn { + runInfo.env = env + return + } + if runInfo.err == ErrBreak { + runInfo.err = nil + } + break + } + } + runInfo.rv = nilValue + runInfo.env = env + + case reflect.Chan: + var chosen int + var ok bool + for { + cases := []reflect.SelectCase{{ + Dir: reflect.SelectRecv, + Chan: reflect.ValueOf(runInfo.ctx.Done()), + }, { + Dir: reflect.SelectRecv, + Chan: value, + }} + chosen, runInfo.rv, ok = reflect.Select(cases) + if chosen == 0 { + runInfo.err = ErrInterrupt + runInfo.rv = nilValue + break + } + if !ok { + break + } + + if runInfo.rv.Kind() == reflect.Interface && !runInfo.rv.IsNil() { + runInfo.rv = runInfo.rv.Elem() + } + if runInfo.rv.Kind() == reflect.Ptr { + runInfo.rv = runInfo.rv.Elem() + } + + runInfo.env.DefineValue(stmt.Vars[0], runInfo.rv) + + runInfo.stmt = stmt.Stmt + runInfo.runSingleStmt() + if runInfo.err != nil { + if runInfo.err == ErrContinue { + runInfo.err = nil + continue + } + if runInfo.err == ErrReturn { + runInfo.env = env + return + } + if runInfo.err == ErrBreak { + runInfo.err = nil + } + break + } + } + runInfo.rv = nilValue + runInfo.env = env + + default: + runInfo.err = newStringError(stmt, "for cannot loop over type "+value.Kind().String()) + runInfo.rv = nilValue + runInfo.env = env + } + + // CForStmt + case *ast.CForStmt: + env := runInfo.env + runInfo.env = env.NewEnv() + + if stmt.Stmt1 != nil { + runInfo.stmt = stmt.Stmt1 + runInfo.runSingleStmt() + if runInfo.err != nil { + runInfo.env = env + return + } + } + + for { + select { + case <-runInfo.ctx.Done(): + runInfo.err = ErrInterrupt + runInfo.rv = nilValue + runInfo.env = env + return + default: + } + + if stmt.Expr2 != nil { + runInfo.expr = stmt.Expr2 + runInfo.invokeExpr() + if runInfo.err != nil { + break + } + if !toBool(runInfo.rv) { + break + } + } + + runInfo.stmt = stmt.Stmt + runInfo.runSingleStmt() + if runInfo.err == ErrContinue { + runInfo.err = nil + } + if runInfo.err != nil { + if runInfo.err == ErrReturn { + runInfo.env = env + return + } + if runInfo.err == ErrBreak { + runInfo.err = nil + } + break + } + + if stmt.Expr3 != nil { + runInfo.expr = stmt.Expr3 + runInfo.invokeExpr() + if runInfo.err != nil { + break + } + } + } + runInfo.rv = nilValue + runInfo.env = env + + // ReturnStmt + case *ast.ReturnStmt: + switch len(stmt.Exprs) { + case 0: + runInfo.rv = nilValue + return + case 1: + runInfo.expr = stmt.Exprs[0] + runInfo.invokeExpr() + return + } + rvs := make([]interface{}, len(stmt.Exprs)) + var i int + for i, runInfo.expr = range stmt.Exprs { + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + rvs[i] = runInfo.rv.Interface() + } + runInfo.rv = reflect.ValueOf(rvs) + + // ThrowStmt + case *ast.ThrowStmt: + runInfo.expr = stmt.Expr + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + runInfo.err = newStringError(stmt, fmt.Sprint(runInfo.rv.Interface())) + + // ModuleStmt + case *ast.ModuleStmt: + e := runInfo.env + runInfo.env, runInfo.err = e.NewModule(stmt.Name) + if runInfo.err != nil { + return + } + runInfo.stmt = stmt.Stmt + runInfo.runSingleStmt() + runInfo.env = e + if runInfo.err != nil { + return + } + runInfo.rv = nilValue + + // SwitchStmt + case *ast.SwitchStmt: + env := runInfo.env + runInfo.env = env.NewEnv() + + runInfo.expr = stmt.Expr + runInfo.invokeExpr() + if runInfo.err != nil { + runInfo.env = env + return + } + value := runInfo.rv + + for _, switchCaseStmt := range stmt.Cases { + caseStmt := switchCaseStmt.(*ast.SwitchCaseStmt) + for _, runInfo.expr = range caseStmt.Exprs { + runInfo.invokeExpr() + if runInfo.err != nil { + runInfo.env = env + return + } + if equal(runInfo.rv, value) { + runInfo.stmt = caseStmt.Stmt + runInfo.runSingleStmt() + runInfo.env = env + return + } + } + } + + if stmt.Default == nil { + runInfo.rv = nilValue + } else { + runInfo.stmt = stmt.Default + runInfo.runSingleStmt() + } + + runInfo.env = env + + // GoroutineStmt + case *ast.GoroutineStmt: + runInfo.expr = stmt.Expr + runInfo.invokeExpr() + + // DeleteStmt + case *ast.DeleteStmt: + runInfo.expr = stmt.Item + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + item := runInfo.rv + + if stmt.Key != nil { + runInfo.expr = stmt.Key + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + } + + if item.Kind() == reflect.Interface && !item.IsNil() { + item = item.Elem() + } + + switch item.Kind() { + case reflect.String: + if stmt.Key != nil && runInfo.rv.Kind() == reflect.Bool && runInfo.rv.Bool() { + runInfo.env.DeleteGlobal(item.String()) + runInfo.rv = nilValue + return + } + runInfo.env.Delete(item.String()) + runInfo.rv = nilValue + + case reflect.Map: + if stmt.Key == nil { + runInfo.err = newStringError(stmt, "second argument to delete cannot be nil for map") + runInfo.rv = nilValue + return + } + if item.IsNil() { + runInfo.rv = nilValue + return + } + runInfo.rv, runInfo.err = convertReflectValueToType(runInfo.rv, item.Type().Key()) + if runInfo.err != nil { + runInfo.err = newStringError(stmt, "cannot use type "+item.Type().Key().String()+" as type "+runInfo.rv.Type().String()+" in delete") + runInfo.rv = nilValue + return + } + item.SetMapIndex(runInfo.rv, reflect.Value{}) + runInfo.rv = nilValue + default: + runInfo.err = newStringError(stmt, "first argument to delete cannot be type "+item.Kind().String()) + runInfo.rv = nilValue + } + + // CloseStmt + case *ast.CloseStmt: + runInfo.expr = stmt.Expr + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + if runInfo.rv.Kind() == reflect.Chan { + runInfo.rv.Close() + runInfo.rv = nilValue + return + } + runInfo.err = newStringError(stmt, "type cannot be "+runInfo.rv.Kind().String()+" for close") + runInfo.rv = nilValue + + // ChanStmt + case *ast.ChanStmt: + runInfo.expr = stmt.RHS + runInfo.invokeExpr() + if runInfo.err != nil { + return + } + if runInfo.rv.Kind() == reflect.Interface && !runInfo.rv.IsNil() { + runInfo.rv = runInfo.rv.Elem() + } + + if runInfo.rv.Kind() != reflect.Chan { + // rhs is not channel + runInfo.err = newStringError(stmt, "receive from non-chan type "+runInfo.rv.Kind().String()) + runInfo.rv = nilValue + return + } + + // rhs is channel + // receive from rhs channel + rhs := runInfo.rv + cases := []reflect.SelectCase{{ + Dir: reflect.SelectRecv, + Chan: reflect.ValueOf(runInfo.ctx.Done()), + }, { + Dir: reflect.SelectRecv, + Chan: rhs, + }} + var chosen int + var ok bool + chosen, runInfo.rv, ok = reflect.Select(cases) + if chosen == 0 { + runInfo.err = ErrInterrupt + runInfo.rv = nilValue + return + } + + rhs = runInfo.rv // store rv in rhs temporarily + + if stmt.OkExpr != nil { + // set ok to OkExpr + if ok { + runInfo.rv = trueValue + } else { + runInfo.rv = falseValue + } + runInfo.expr = stmt.OkExpr + runInfo.invokeLetExpr() + // TODO: ok to ignore error? + } + + if ok { + // set rv to lhs + runInfo.rv = rhs + runInfo.expr = stmt.LHS + runInfo.invokeLetExpr() + if runInfo.err != nil { + return + } + } else { + runInfo.rv = nilValue + } + + // default + default: + runInfo.err = newStringError(stmt, "unknown statement") + runInfo.rv = nilValue + } + +} diff --git a/src/tool/run/vm/vmToX.go b/src/tool/run/vm/vmToX.go new file mode 100644 index 0000000..0854c91 --- /dev/null +++ b/src/tool/run/vm/vmToX.go @@ -0,0 +1,178 @@ +package vm + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" +) + +// toString converts all reflect.Value-s into string. +func toString(v reflect.Value) string { + if v.Kind() == reflect.Interface && !v.IsNil() { + v = v.Elem() + } + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + if v.Kind() == reflect.String { + return v.String() + } + return fmt.Sprint(v.Interface()) +} + +// toBool converts all reflect.Value-s into bool. +func toBool(v reflect.Value) bool { + b, _ := tryToBool(v) + return b +} + +// tryToBool attempts to convert the value 'v' to a boolean, returning +// an error if it cannot. When converting a string, the function returns +// true if the string nonempty and does not satisfy the condition for false +// with parseBool https://golang.org/pkg/strconv/#ParseBool +// and is not 0.0 +func tryToBool(v reflect.Value) (bool, error) { + if v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { + v = v.Elem() + } + switch v.Kind() { + case reflect.Float64, reflect.Float32: + return v.Float() != 0, nil + case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int: + return v.Int() != 0, nil + case reflect.Bool: + return v.Bool(), nil + case reflect.String: + if v.Len() == 0 { + return false, nil + } + + s := v.String() + if b, err := strconv.ParseBool(s); err == nil && !b { + return false, nil + } + + if f, err := tryToFloat64(v); err == nil && f == 0 { + return false, nil + } + return true, nil + case reflect.Slice, reflect.Map: + if v.Len() > 0 { + return true, nil + } + return false, nil + } + return false, errors.New("unknown type") +} + +// toFloat64 converts all reflect.Value-s into float64. +func toFloat64(v reflect.Value) float64 { + f, _ := tryToFloat64(v) + return f +} + +// tryToFloat64 attempts to convert a value to a float64. +// If it cannot (in the case of a non-numeric string, a struct, etc.) +// it returns 0.0 and an error. +func tryToFloat64(v reflect.Value) (float64, error) { + if v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { + v = v.Elem() + } + switch v.Kind() { + case reflect.Float64, reflect.Float32: + return v.Float(), nil + case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int: + return float64(v.Int()), nil + case reflect.Bool: + if v.Bool() { + return 1, nil + } + return 0, nil + case reflect.String: + f, err := strconv.ParseFloat(v.String(), 64) + if err == nil { + return f, nil + } + } + return 0.0, errors.New("couldn't convert to a float64") +} + +// toInt64 converts all reflect.Value-s into int64. +func toInt64(v reflect.Value) int64 { + i, _ := tryToInt64(v) + return i +} + +// tryToInt64 attempts to convert a value to an int64. +// If it cannot (in the case of a non-numeric string, a struct, etc.) +// it returns 0 and an error. +func tryToInt64(v reflect.Value) (int64, error) { + if v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { + v = v.Elem() + } + switch v.Kind() { + case reflect.Float64, reflect.Float32: + return int64(v.Float()), nil + case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int: + return v.Int(), nil + case reflect.Bool: + if v.Bool() { + return 1, nil + } + return 0, nil + case reflect.String: + s := v.String() + var i int64 + var err error + if strings.HasPrefix(s, "0x") { + i, err = strconv.ParseInt(s, 16, 64) + } else { + i, err = strconv.ParseInt(s, 10, 64) + } + if err == nil { + return i, nil + } + } + return 0, errors.New("couldn't convert to integer") +} + +// toInt converts all reflect.Value-s into int. +func toInt(v reflect.Value) int { + i, _ := tryToInt(v) + return i +} + +// tryToInt attempts to convert a value to an int. +// If it cannot (in the case of a non-numeric string, a struct, etc.) +// it returns 0 and an error. +func tryToInt(v reflect.Value) (int, error) { + if v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { + v = v.Elem() + } + switch v.Kind() { + case reflect.Float64, reflect.Float32: + return int(v.Float()), nil + case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int: + return int(v.Int()), nil + case reflect.Bool: + if v.Bool() { + return 1, nil + } + return 0, nil + case reflect.String: + s := v.String() + var i int64 + var err error + if strings.HasPrefix(s, "0x") { + i, err = strconv.ParseInt(s, 16, 64) + } else { + i, err = strconv.ParseInt(s, 10, 64) + } + if err == nil { + return int(i), nil + } + } + return 0, errors.New("couldn't convert to integer") +} diff --git a/src/tool/run/vm/vm_Go19_test.go b/src/tool/run/vm/vm_Go19_test.go new file mode 100644 index 0000000..5ace198 --- /dev/null +++ b/src/tool/run/vm/vm_Go19_test.go @@ -0,0 +1,17 @@ +// +build go1.9 + +package vm + +import ( + "fmt" + "testing" +) + +func TestMakeGo19(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `make(struct { a int64 })`, RunError: fmt.Errorf("reflect.StructOf: field \"a\" is unexported but missing PkgPath")}, + } + runTests(t, tests, nil, &Options{Debug: false}) +} diff --git a/src/tool/run/vm/vm_NotGo19_test.go b/src/tool/run/vm/vm_NotGo19_test.go new file mode 100644 index 0000000..4c262d8 --- /dev/null +++ b/src/tool/run/vm/vm_NotGo19_test.go @@ -0,0 +1,17 @@ +// +build !go1.9 + +package vm + +import ( + "fmt" + "testing" +) + +func TestMakeNotGo19(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `make(struct { a int64 })`, RunError: fmt.Errorf("reflect.StructOf: field \"a\" is unexported but has no PkgPath")}, + } + runTests(t, tests, nil, &Options{Debug: false}) +} diff --git a/src/tool/run/vm/vm_test.go b/src/tool/run/vm/vm_test.go new file mode 100644 index 0000000..55af9c9 --- /dev/null +++ b/src/tool/run/vm/vm_test.go @@ -0,0 +1,1268 @@ +package vm + +import ( + "context" + "fmt" + "io" + "os" + "reflect" + "sync" + "testing" + "time" + + "github.com/surdeus/goblin/src/tool/run/ast" + "github.com/surdeus/goblin/src/tool/run/env" +) + +func TestNumbers(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: ``}, + {Script: `;`}, + {Script: ` +`}, + {Script: ` +1 +`, RunOutput: int64(1)}, + + {Script: `1..1`, ParseError: fmt.Errorf("invalid number: 1..1")}, + {Script: `1e.1`, ParseError: fmt.Errorf("invalid number: 1e.1")}, + {Script: `1ee1`, ParseError: fmt.Errorf("syntax error")}, + {Script: `1e+e1`, ParseError: fmt.Errorf("syntax error")}, + {Script: `0x1g`, ParseError: fmt.Errorf("syntax error")}, + {Script: `9223372036854775808`, ParseError: fmt.Errorf("invalid number: 9223372036854775808")}, + {Script: `-9223372036854775809`, ParseError: fmt.Errorf("invalid number: -9223372036854775809")}, + + {Script: `1`, RunOutput: int64(1)}, + {Script: `-1`, RunOutput: int64(-1)}, + {Script: `9223372036854775807`, RunOutput: int64(9223372036854775807)}, + {Script: `-9223372036854775808`, RunOutput: int64(-9223372036854775808)}, + {Script: `-9223372036854775807-1`, RunOutput: int64(-9223372036854775808)}, + {Script: `-9223372036854775807 -1`, RunOutput: int64(-9223372036854775808)}, + {Script: `-9223372036854775807 - 1`, RunOutput: int64(-9223372036854775808)}, + {Script: `1.1`, RunOutput: float64(1.1)}, + {Script: `-1.1`, RunOutput: float64(-1.1)}, + + {Script: `1e1`, RunOutput: float64(10)}, + {Script: `1.5e1`, RunOutput: float64(15)}, + {Script: `1e-1`, RunOutput: float64(0.1)}, + + {Script: `-1e1`, RunOutput: float64(-10)}, + {Script: `-1.5e1`, RunOutput: float64(-15)}, + {Script: `-1e-1`, RunOutput: float64(-0.1)}, + + {Script: `0x1`, RunOutput: int64(1)}, + {Script: `0xa`, RunOutput: int64(10)}, + {Script: `0xb`, RunOutput: int64(11)}, + {Script: `0xc`, RunOutput: int64(12)}, + {Script: `0xe`, RunOutput: int64(14)}, + {Script: `0xf`, RunOutput: int64(15)}, + {Script: `0Xf`, RunOutput: int64(15)}, + {Script: `0XF`, RunOutput: int64(15)}, + {Script: `0x7FFFFFFFFFFFFFFF`, RunOutput: int64(9223372036854775807)}, + + {Script: `-0x1`, RunOutput: int64(-1)}, + {Script: `-0xc`, RunOutput: int64(-12)}, + {Script: `-0xe`, RunOutput: int64(-14)}, + {Script: `-0xf`, RunOutput: int64(-15)}, + {Script: `-0Xf`, RunOutput: int64(-15)}, + {Script: `-0x7FFFFFFFFFFFFFFF`, RunOutput: int64(-9223372036854775807)}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestStrings(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `a`, Input: map[string]interface{}{"a": 'a'}, RunOutput: 'a', Output: map[string]interface{}{"a": 'a'}}, + {Script: `a.b`, Input: map[string]interface{}{"a": 'a'}, RunError: fmt.Errorf("type int32 does not support member operation"), Output: map[string]interface{}{"a": 'a'}}, + {Script: `a[0]`, Input: map[string]interface{}{"a": 'a'}, RunError: fmt.Errorf("type int32 does not support index operation"), RunOutput: nil, Output: map[string]interface{}{"a": 'a'}}, + {Script: `a[0:1]`, Input: map[string]interface{}{"a": 'a'}, RunError: fmt.Errorf("type int32 does not support slice operation"), RunOutput: nil, Output: map[string]interface{}{"a": 'a'}}, + + {Script: `a.b = "a"`, Input: map[string]interface{}{"a": 'a'}, RunError: fmt.Errorf("type int32 does not support member operation"), RunOutput: nil, Output: map[string]interface{}{"a": 'a'}}, + {Script: `a[0] = "a"`, Input: map[string]interface{}{"a": 'a'}, RunError: fmt.Errorf("type int32 does not support index operation"), RunOutput: nil, Output: map[string]interface{}{"a": 'a'}}, + {Script: `a[0:1] = "a"`, Input: map[string]interface{}{"a": 'a'}, RunError: fmt.Errorf("type int32 does not support slice operation"), RunOutput: nil, Output: map[string]interface{}{"a": 'a'}}, + + {Script: `a.b = "a"`, Input: map[string]interface{}{"a": "test"}, RunError: fmt.Errorf("type string does not support member operation"), Output: map[string]interface{}{"a": "test"}}, + {Script: `a[0:1] = "a"`, Input: map[string]interface{}{"a": "test"}, RunError: fmt.Errorf("type string does not support slice operation for assignment"), Output: map[string]interface{}{"a": "test"}}, + + {Script: `a`, Input: map[string]interface{}{"a": "test"}, RunOutput: "test", Output: map[string]interface{}{"a": "test"}}, + {Script: `a["a"]`, Input: map[string]interface{}{"a": "test"}, RunError: fmt.Errorf("index must be a number"), Output: map[string]interface{}{"a": "test"}}, + {Script: `a[0]`, Input: map[string]interface{}{"a": ""}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": ""}}, + {Script: `a[-1]`, Input: map[string]interface{}{"a": "test"}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": "test"}}, + {Script: `a[0]`, Input: map[string]interface{}{"a": "test"}, RunOutput: "t", Output: map[string]interface{}{"a": "test"}}, + {Script: `a[1]`, Input: map[string]interface{}{"a": "test"}, RunOutput: "e", Output: map[string]interface{}{"a": "test"}}, + {Script: `a[3]`, Input: map[string]interface{}{"a": "test"}, RunOutput: "t", Output: map[string]interface{}{"a": "test"}}, + {Script: `a[4]`, Input: map[string]interface{}{"a": "test"}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": "test"}}, + + {Script: `a`, Input: map[string]interface{}{"a": `"a"`}, RunOutput: `"a"`, Output: map[string]interface{}{"a": `"a"`}}, + {Script: `a[0]`, Input: map[string]interface{}{"a": `"a"`}, RunOutput: `"`, Output: map[string]interface{}{"a": `"a"`}}, + {Script: `a[1]`, Input: map[string]interface{}{"a": `"a"`}, RunOutput: "a", Output: map[string]interface{}{"a": `"a"`}}, + + {Script: `a = "\"a\""`, RunOutput: `"a"`, Output: map[string]interface{}{"a": `"a"`}}, + {Script: `a = "\"a\""; a`, RunOutput: `"a"`, Output: map[string]interface{}{"a": `"a"`}}, + {Script: `a = "\"a\""; a[0]`, RunOutput: `"`, Output: map[string]interface{}{"a": `"a"`}}, + {Script: `a = "\"a\""; a[1]`, RunOutput: "a", Output: map[string]interface{}{"a": `"a"`}}, + + {Script: `a`, Input: map[string]interface{}{"a": "a\\b"}, RunOutput: "a\\b", Output: map[string]interface{}{"a": "a\\b"}}, + {Script: `a`, Input: map[string]interface{}{"a": "a\\\\b"}, RunOutput: "a\\\\b", Output: map[string]interface{}{"a": "a\\\\b"}}, + {Script: `a = "a\b"`, RunOutput: "a\b", Output: map[string]interface{}{"a": "a\b"}}, + {Script: `a = "a\\b"`, RunOutput: "a\\b", Output: map[string]interface{}{"a": "a\\b"}}, + + {Script: `a[:]`, Input: map[string]interface{}{"a": "test data"}, ParseError: fmt.Errorf("syntax error"), Output: map[string]interface{}{"a": "test data"}}, + + {Script: `a[0:]`, Input: map[string]interface{}{"a": ""}, RunOutput: "", Output: map[string]interface{}{"a": ""}}, + {Script: `a[1:]`, Input: map[string]interface{}{"a": ""}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": ""}}, + {Script: `a[:0]`, Input: map[string]interface{}{"a": ""}, RunOutput: "", Output: map[string]interface{}{"a": ""}}, + {Script: `a[:1]`, Input: map[string]interface{}{"a": ""}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": ""}}, + {Script: `a[0:0]`, Input: map[string]interface{}{"a": ""}, RunOutput: "", Output: map[string]interface{}{"a": ""}}, + + {Script: `a[1:0]`, Input: map[string]interface{}{"a": "test data"}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[-1:2]`, Input: map[string]interface{}{"a": "test data"}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[1:-2]`, Input: map[string]interface{}{"a": "test data"}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[-1:]`, Input: map[string]interface{}{"a": "test data"}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[:-2]`, Input: map[string]interface{}{"a": "test data"}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": "test data"}}, + + {Script: `a[0:0]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[0:1]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "t", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[0:2]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "te", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[0:3]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "tes", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[0:7]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "test da", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[0:8]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "test dat", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[0:9]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "test data", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[0:10]`, Input: map[string]interface{}{"a": "test data"}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": "test data"}}, + + {Script: `a[1:1]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[1:2]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "e", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[1:3]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "es", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[1:7]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "est da", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[1:8]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "est dat", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[1:9]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "est data", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[1:10]`, Input: map[string]interface{}{"a": "test data"}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": "test data"}}, + + {Script: `a[0:9]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "test data", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[1:9]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "est data", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[2:9]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "st data", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[3:9]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "t data", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[7:9]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "ta", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[8:9]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "a", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[9:9]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "", Output: map[string]interface{}{"a": "test data"}}, + + {Script: `a[:0]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[:1]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "t", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[:2]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "te", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[:3]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "tes", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[:7]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "test da", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[:8]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "test dat", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[:9]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "test data", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[:10]`, Input: map[string]interface{}{"a": "test data"}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": "test data"}}, + + {Script: `a[0:]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "test data", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[1:]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "est data", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[2:]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "st data", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[3:]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "t data", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[7:]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "ta", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[8:]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "a", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[9:]`, Input: map[string]interface{}{"a": "test data"}, RunOutput: "", Output: map[string]interface{}{"a": "test data"}}, + {Script: `a[10:]`, Input: map[string]interface{}{"a": "test data"}, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": "test data"}}, + + // index assignment - len 0 + {Script: `a = ""; a[0] = "x"`, RunOutput: "x", Output: map[string]interface{}{"a": "x"}}, + {Script: `a = ""; a[1] = "x"`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": ""}}, + + // index assignment - len 1 + {Script: `a = "a"; a[0] = "x"`, RunOutput: "x", Output: map[string]interface{}{"a": "x"}}, + {Script: `a = "a"; a[1] = "x"`, RunOutput: "x", Output: map[string]interface{}{"a": "ax"}}, + {Script: `a = "a"; a[2] = "x"`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": "a"}}, + + // index assignment - len 2 + {Script: `a = "ab"; a[0] = "x"`, RunOutput: "x", Output: map[string]interface{}{"a": "xb"}}, + {Script: `a = "ab"; a[1] = "x"`, RunOutput: "x", Output: map[string]interface{}{"a": "ax"}}, + {Script: `a = "ab"; a[2] = "x"`, RunOutput: "x", Output: map[string]interface{}{"a": "abx"}}, + {Script: `a = "ab"; a[3] = "x"`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": "ab"}}, + + // index assignment - len 3 + {Script: `a = "abc"; a[0] = "x"`, RunOutput: "x", Output: map[string]interface{}{"a": "xbc"}}, + {Script: `a = "abc"; a[1] = "x"`, RunOutput: "x", Output: map[string]interface{}{"a": "axc"}}, + {Script: `a = "abc"; a[2] = "x"`, RunOutput: "x", Output: map[string]interface{}{"a": "abx"}}, + {Script: `a = "abc"; a[3] = "x"`, RunOutput: "x", Output: map[string]interface{}{"a": "abcx"}}, + {Script: `a = "abc"; a[4] = "x"`, RunError: fmt.Errorf("index out of range"), Output: map[string]interface{}{"a": "abc"}}, + + // index assignment - vm types + {Script: `a = "abc"; a[1] = nil`, RunOutput: nil, Output: map[string]interface{}{"a": "ac"}}, + {Script: `a = "abc"; a[1] = true`, RunError: fmt.Errorf("type bool cannot be assigned to type string"), Output: map[string]interface{}{"a": "abc"}}, + {Script: `a = "abc"; a[1] = 120`, RunOutput: int64(120), Output: map[string]interface{}{"a": "axc"}}, + {Script: `a = "abc"; a[1] = 2.2`, RunError: fmt.Errorf("type float64 cannot be assigned to type string"), Output: map[string]interface{}{"a": "abc"}}, + {Script: `a = "abc"; a[1] = ["a"]`, RunError: fmt.Errorf("type []interface {} cannot be assigned to type string"), Output: map[string]interface{}{"a": "abc"}}, + + // index assignment - Go types + {Script: `a = "abc"; a[1] = b`, Input: map[string]interface{}{"b": reflect.Value{}}, RunError: fmt.Errorf("type reflect.Value cannot be assigned to type string"), Output: map[string]interface{}{"a": "abc"}}, + {Script: `a = "abc"; a[1] = b`, Input: map[string]interface{}{"b": nil}, RunOutput: nil, Output: map[string]interface{}{"a": "ac"}}, + {Script: `a = "abc"; a[1] = b`, Input: map[string]interface{}{"b": true}, RunError: fmt.Errorf("type bool cannot be assigned to type string"), Output: map[string]interface{}{"a": "abc"}}, + {Script: `a = "abc"; a[1] = b`, Input: map[string]interface{}{"b": int32(120)}, RunOutput: int32(120), Output: map[string]interface{}{"a": "axc"}}, + {Script: `a = "abc"; a[1] = b`, Input: map[string]interface{}{"b": int64(120)}, RunOutput: int64(120), Output: map[string]interface{}{"a": "axc"}}, + {Script: `a = "abc"; a[1] = b`, Input: map[string]interface{}{"b": float32(1.1)}, RunError: fmt.Errorf("type float32 cannot be assigned to type string"), Output: map[string]interface{}{"a": "abc"}}, + {Script: `a = "abc"; a[1] = b`, Input: map[string]interface{}{"b": float64(2.2)}, RunError: fmt.Errorf("type float64 cannot be assigned to type string"), Output: map[string]interface{}{"a": "abc"}}, + {Script: `a = "abc"; a[1] = b`, Input: map[string]interface{}{"b": "x"}, RunOutput: "x", Output: map[string]interface{}{"a": "axc"}}, + {Script: `a = "abc"; a[1] = b`, Input: map[string]interface{}{"b": 'x'}, RunOutput: 'x', Output: map[string]interface{}{"a": "axc"}}, + {Script: `a = "abc"; a[1] = b`, Input: map[string]interface{}{"b": struct{}{}}, RunError: fmt.Errorf("type struct {} cannot be assigned to type string"), Output: map[string]interface{}{"a": "abc"}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestVar(t *testing.T) { + t.Parallel() + + testInput1 := map[string]interface{}{"b": func() {}} + tests := []Test{ + // simple one variable + {Script: `1 = 2`, RunError: fmt.Errorf("invalid operation")}, + {Script: `var 1 = 2`, ParseError: fmt.Errorf("syntax error")}, + {Script: `a = 1++`, RunError: fmt.Errorf("invalid operation")}, + {Script: `var a = 1++`, RunError: fmt.Errorf("invalid operation")}, + {Script: `a := 1`, ParseError: fmt.Errorf("syntax error")}, + {Script: `var a := 1`, ParseError: fmt.Errorf("syntax error")}, + {Script: `y = z`, RunError: fmt.Errorf("undefined symbol 'z'")}, + + {Script: `a = nil`, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + {Script: `a = true`, RunOutput: true, Output: map[string]interface{}{"a": true}}, + {Script: `a = 1`, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1.1`, RunOutput: float64(1.1), Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `a = "a"`, RunOutput: "a", Output: map[string]interface{}{"a": "a"}}, + + {Script: `var a = nil`, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + {Script: `var a = true`, RunOutput: true, Output: map[string]interface{}{"a": true}}, + {Script: `var a = 1`, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `var a = 1.1`, RunOutput: float64(1.1), Output: map[string]interface{}{"a": float64(1.1)}}, + {Script: `var a = "a"`, RunOutput: "a", Output: map[string]interface{}{"a": "a"}}, + + {Script: `a = b`, Input: map[string]interface{}{"b": reflect.Value{}}, RunOutput: reflect.Value{}, Output: map[string]interface{}{"a": reflect.Value{}, "b": reflect.Value{}}}, + {Script: `a = b`, Input: map[string]interface{}{"b": nil}, RunOutput: nil, Output: map[string]interface{}{"a": nil, "b": nil}}, + {Script: `a = b`, Input: map[string]interface{}{"b": true}, RunOutput: true, Output: map[string]interface{}{"a": true, "b": true}}, + {Script: `a = b`, Input: map[string]interface{}{"b": int32(1)}, RunOutput: int32(1), Output: map[string]interface{}{"a": int32(1), "b": int32(1)}}, + {Script: `a = b`, Input: map[string]interface{}{"b": int64(1)}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1), "b": int64(1)}}, + {Script: `a = b`, Input: map[string]interface{}{"b": float32(1.1)}, RunOutput: float32(1.1), Output: map[string]interface{}{"a": float32(1.1), "b": float32(1.1)}}, + {Script: `a = b`, Input: map[string]interface{}{"b": float64(1.1)}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": float64(1.1), "b": float64(1.1)}}, + {Script: `a = b`, Input: map[string]interface{}{"b": "a"}, RunOutput: "a", Output: map[string]interface{}{"a": "a", "b": "a"}}, + {Script: `a = b`, Input: map[string]interface{}{"b": 'a'}, RunOutput: 'a', Output: map[string]interface{}{"a": 'a', "b": 'a'}}, + {Script: `a = b`, Input: map[string]interface{}{"b": struct{}{}}, RunOutput: struct{}{}, Output: map[string]interface{}{"a": struct{}{}, "b": struct{}{}}}, + + {Script: `var a = b`, Input: map[string]interface{}{"b": reflect.Value{}}, RunOutput: reflect.Value{}, Output: map[string]interface{}{"a": reflect.Value{}, "b": reflect.Value{}}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": nil}, RunOutput: nil, Output: map[string]interface{}{"a": nil, "b": nil}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": true}, RunOutput: true, Output: map[string]interface{}{"a": true, "b": true}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": int32(1)}, RunOutput: int32(1), Output: map[string]interface{}{"a": int32(1), "b": int32(1)}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": int64(1)}, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1), "b": int64(1)}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": float32(1.1)}, RunOutput: float32(1.1), Output: map[string]interface{}{"a": float32(1.1), "b": float32(1.1)}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": float64(1.1)}, RunOutput: float64(1.1), Output: map[string]interface{}{"a": float64(1.1), "b": float64(1.1)}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": "a"}, RunOutput: "a", Output: map[string]interface{}{"a": "a", "b": "a"}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": 'a'}, RunOutput: 'a', Output: map[string]interface{}{"a": 'a', "b": 'a'}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": struct{}{}}, RunOutput: struct{}{}, Output: map[string]interface{}{"a": struct{}{}, "b": struct{}{}}}, + + // simple one variable overwrite + {Script: `a = true; a = nil`, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + {Script: `a = nil; a = true`, RunOutput: true, Output: map[string]interface{}{"a": true}}, + {Script: `a = 1; a = 2`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 1.1; a = 2.2`, RunOutput: float64(2.2), Output: map[string]interface{}{"a": float64(2.2)}}, + {Script: `a = "a"; a = "b"`, RunOutput: "b", Output: map[string]interface{}{"a": "b"}}, + + {Script: `var a = true; var a = nil`, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + {Script: `var a = nil; var a = true`, RunOutput: true, Output: map[string]interface{}{"a": true}}, + {Script: `var a = 1; var a = 2`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `var a = 1.1; var a = 2.2`, RunOutput: float64(2.2), Output: map[string]interface{}{"a": float64(2.2)}}, + {Script: `var a = "a"; var a = "b"`, RunOutput: "b", Output: map[string]interface{}{"a": "b"}}, + + {Script: `a = nil`, Input: map[string]interface{}{"a": true}, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + {Script: `a = true`, Input: map[string]interface{}{"a": nil}, RunOutput: true, Output: map[string]interface{}{"a": true}}, + {Script: `a = 2`, Input: map[string]interface{}{"a": int32(1)}, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 2`, Input: map[string]interface{}{"a": int64(1)}, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 2.2`, Input: map[string]interface{}{"a": float32(1.1)}, RunOutput: float64(2.2), Output: map[string]interface{}{"a": float64(2.2)}}, + {Script: `a = 2.2`, Input: map[string]interface{}{"a": float64(1.1)}, RunOutput: float64(2.2), Output: map[string]interface{}{"a": float64(2.2)}}, + {Script: `a = "b"`, Input: map[string]interface{}{"a": "a"}, RunOutput: "b", Output: map[string]interface{}{"a": "b"}}, + + {Script: `var a = nil`, Input: map[string]interface{}{"a": true}, RunOutput: nil, Output: map[string]interface{}{"a": nil}}, + {Script: `var a = true`, Input: map[string]interface{}{"a": nil}, RunOutput: true, Output: map[string]interface{}{"a": true}}, + {Script: `var a = 2`, Input: map[string]interface{}{"a": int32(1)}, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `var a = 2`, Input: map[string]interface{}{"a": int64(1)}, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `var a = 2.2`, Input: map[string]interface{}{"a": float32(1.1)}, RunOutput: float64(2.2), Output: map[string]interface{}{"a": float64(2.2)}}, + {Script: `var a = 2.2`, Input: map[string]interface{}{"a": float64(1.1)}, RunOutput: float64(2.2), Output: map[string]interface{}{"a": float64(2.2)}}, + {Script: `var a = "b"`, Input: map[string]interface{}{"a": "a"}, RunOutput: "b", Output: map[string]interface{}{"a": "b"}}, + + // Go variable copy + {Script: `a = b`, Input: testInput1, RunOutput: testInput1["b"], Output: map[string]interface{}{"a": testInput1["b"], "b": testInput1["b"]}}, + {Script: `var a = b`, Input: testInput1, RunOutput: testInput1["b"], Output: map[string]interface{}{"a": testInput1["b"], "b": testInput1["b"]}}, + + {Script: `a = b`, Input: map[string]interface{}{"b": testVarValue}, RunOutput: testVarValue, Output: map[string]interface{}{"a": testVarValue, "b": testVarValue}}, + {Script: `a = b`, Input: map[string]interface{}{"b": testVarBoolP}, RunOutput: testVarBoolP, Output: map[string]interface{}{"a": testVarBoolP, "b": testVarBoolP}}, + {Script: `a = b`, Input: map[string]interface{}{"b": testVarInt32P}, RunOutput: testVarInt32P, Output: map[string]interface{}{"a": testVarInt32P, "b": testVarInt32P}}, + {Script: `a = b`, Input: map[string]interface{}{"b": testVarInt64P}, RunOutput: testVarInt64P, Output: map[string]interface{}{"a": testVarInt64P, "b": testVarInt64P}}, + {Script: `a = b`, Input: map[string]interface{}{"b": testVarFloat32P}, RunOutput: testVarFloat32P, Output: map[string]interface{}{"a": testVarFloat32P, "b": testVarFloat32P}}, + {Script: `a = b`, Input: map[string]interface{}{"b": testVarFloat64P}, RunOutput: testVarFloat64P, Output: map[string]interface{}{"a": testVarFloat64P, "b": testVarFloat64P}}, + {Script: `a = b`, Input: map[string]interface{}{"b": testVarStringP}, RunOutput: testVarStringP, Output: map[string]interface{}{"a": testVarStringP, "b": testVarStringP}}, + {Script: `a = b`, Input: map[string]interface{}{"b": testVarFuncP}, RunOutput: testVarFuncP, Output: map[string]interface{}{"a": testVarFuncP, "b": testVarFuncP}}, + + {Script: `var a = b`, Input: map[string]interface{}{"b": testVarValue}, RunOutput: testVarValue, Output: map[string]interface{}{"a": testVarValue, "b": testVarValue}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": testVarBoolP}, RunOutput: testVarBoolP, Output: map[string]interface{}{"a": testVarBoolP, "b": testVarBoolP}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": testVarInt32P}, RunOutput: testVarInt32P, Output: map[string]interface{}{"a": testVarInt32P, "b": testVarInt32P}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": testVarInt64P}, RunOutput: testVarInt64P, Output: map[string]interface{}{"a": testVarInt64P, "b": testVarInt64P}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": testVarFloat32P}, RunOutput: testVarFloat32P, Output: map[string]interface{}{"a": testVarFloat32P, "b": testVarFloat32P}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": testVarFloat64P}, RunOutput: testVarFloat64P, Output: map[string]interface{}{"a": testVarFloat64P, "b": testVarFloat64P}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": testVarStringP}, RunOutput: testVarStringP, Output: map[string]interface{}{"a": testVarStringP, "b": testVarStringP}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": testVarFuncP}, RunOutput: testVarFuncP, Output: map[string]interface{}{"a": testVarFuncP, "b": testVarFuncP}}, + + {Script: `a = b`, Input: map[string]interface{}{"b": testVarValueBool}, RunOutput: testVarValueBool, Output: map[string]interface{}{"a": testVarValueBool, "b": testVarValueBool}}, + {Script: `a = b`, Input: map[string]interface{}{"b": testVarValueInt32}, RunOutput: testVarValueInt32, Output: map[string]interface{}{"a": testVarValueInt32, "b": testVarValueInt32}}, + {Script: `a = b`, Input: map[string]interface{}{"b": testVarValueInt64}, RunOutput: testVarValueInt64, Output: map[string]interface{}{"a": testVarValueInt64, "b": testVarValueInt64}}, + {Script: `a = b`, Input: map[string]interface{}{"b": testVarValueFloat32}, RunOutput: testVarValueFloat32, Output: map[string]interface{}{"a": testVarValueFloat32, "b": testVarValueFloat32}}, + {Script: `a = b`, Input: map[string]interface{}{"b": testVarValueFloat64}, RunOutput: testVarValueFloat64, Output: map[string]interface{}{"a": testVarValueFloat64, "b": testVarValueFloat64}}, + {Script: `a = b`, Input: map[string]interface{}{"b": testVarValueString}, RunOutput: testVarValueString, Output: map[string]interface{}{"a": testVarValueString, "b": testVarValueString}}, + + {Script: `var a = b`, Input: map[string]interface{}{"b": testVarValueBool}, RunOutput: testVarValueBool, Output: map[string]interface{}{"a": testVarValueBool, "b": testVarValueBool}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": testVarValueInt32}, RunOutput: testVarValueInt32, Output: map[string]interface{}{"a": testVarValueInt32, "b": testVarValueInt32}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": testVarValueInt64}, RunOutput: testVarValueInt64, Output: map[string]interface{}{"a": testVarValueInt64, "b": testVarValueInt64}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": testVarValueFloat32}, RunOutput: testVarValueFloat32, Output: map[string]interface{}{"a": testVarValueFloat32, "b": testVarValueFloat32}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": testVarValueFloat64}, RunOutput: testVarValueFloat64, Output: map[string]interface{}{"a": testVarValueFloat64, "b": testVarValueFloat64}}, + {Script: `var a = b`, Input: map[string]interface{}{"b": testVarValueString}, RunOutput: testVarValueString, Output: map[string]interface{}{"a": testVarValueString, "b": testVarValueString}}, + + // one variable spacing + {Script: ` +a = 1 +`, RunOutput: int64(1)}, + {Script: ` +a = 1; +`, RunOutput: int64(1)}, + + // one variable many values + {Script: `, b = 1, 2`, ParseError: fmt.Errorf("syntax error: unexpected ','"), RunOutput: int64(2), Output: map[string]interface{}{"b": int64(1)}}, + {Script: `var , b = 1, 2`, ParseError: fmt.Errorf("syntax error: unexpected ','"), RunOutput: int64(2), Output: map[string]interface{}{"b": int64(1)}}, + {Script: `a, = 1, 2`, ParseError: fmt.Errorf("syntax error")}, + {Script: `var a, = 1, 2`, ParseError: fmt.Errorf("syntax error")}, + + // TOFIX: should not error? + {Script: `a = 1, 2`, ParseError: fmt.Errorf("syntax error")}, + {Script: `var a = 1, 2`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(1)}}, + // TOFIX: should not error? + {Script: `a = 1, 2, 3`, ParseError: fmt.Errorf("syntax error")}, + {Script: `var a = 1, 2, 3`, RunOutput: int64(3), Output: map[string]interface{}{"a": int64(1)}}, + + // two variables many values + {Script: `a, b = 1,`, ParseError: fmt.Errorf("syntax error")}, + {Script: `var a, b = 1,`, ParseError: fmt.Errorf("syntax error")}, + {Script: `a, b = ,1`, ParseError: fmt.Errorf("syntax error: unexpected ','"), RunOutput: int64(1)}, + {Script: `var a, b = ,1`, ParseError: fmt.Errorf("syntax error: unexpected ','"), RunOutput: int64(1)}, + {Script: `a, b = 1,,`, ParseError: fmt.Errorf("syntax error")}, + {Script: `var a, b = 1,,`, ParseError: fmt.Errorf("syntax error")}, + {Script: `a, b = ,1,`, ParseError: fmt.Errorf("syntax error")}, + {Script: `var a, b = ,1,`, ParseError: fmt.Errorf("syntax error")}, + {Script: `a, b = ,,1`, ParseError: fmt.Errorf("syntax error")}, + {Script: `var a, b = ,,1`, ParseError: fmt.Errorf("syntax error")}, + + {Script: `a.c, b = 1, 2`, RunError: fmt.Errorf("undefined symbol 'a'")}, + {Script: `a, b.c = 1, 2`, RunError: fmt.Errorf("undefined symbol 'b'")}, + + {Script: `a, b = 1`, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `var a, b = 1`, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a, b = 1, 2`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(1), "b": int64(2)}}, + {Script: `var a, b = 1, 2`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(1), "b": int64(2)}}, + {Script: `a, b = 1, 2, 3`, RunOutput: int64(3), Output: map[string]interface{}{"a": int64(1), "b": int64(2)}}, + {Script: `var a, b = 1, 2, 3`, RunOutput: int64(3), Output: map[string]interface{}{"a": int64(1), "b": int64(2)}}, + + // three variables many values + {Script: `a, b, c = 1`, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `var a, b, c = 1`, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a, b, c = 1, 2`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(1), "b": int64(2)}}, + {Script: `var a, b, c = 1, 2`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(1), "b": int64(2)}}, + {Script: `a, b, c = 1, 2, 3`, RunOutput: int64(3), Output: map[string]interface{}{"a": int64(1), "b": int64(2), "c": int64(3)}}, + {Script: `var a, b, c = 1, 2, 3`, RunOutput: int64(3), Output: map[string]interface{}{"a": int64(1), "b": int64(2), "c": int64(3)}}, + {Script: `a, b, c = 1, 2, 3, 4`, RunOutput: int64(4), Output: map[string]interface{}{"a": int64(1), "b": int64(2), "c": int64(3)}}, + {Script: `var a, b, c = 1, 2, 3, 4`, RunOutput: int64(4), Output: map[string]interface{}{"a": int64(1), "b": int64(2), "c": int64(3)}}, + + // scope + {Script: `func(){ a = 1 }(); a`, RunError: fmt.Errorf("undefined symbol 'a'")}, + {Script: `func(){ var a = 1 }(); a`, RunError: fmt.Errorf("undefined symbol 'a'")}, + + {Script: `a = 1; func(){ a = 2 }()`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `var a = 1; func(){ a = 2 }()`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + {Script: `a = 1; func(){ var a = 2 }()`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `var a = 1; func(){ var a = 2 }()`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(1)}}, + + // function return + {Script: `a, 1++ = func(){ return 1, 2 }()`, RunError: fmt.Errorf("invalid operation"), Output: map[string]interface{}{"a": int64(1)}}, + + {Script: `a = func(){ return 1 }()`, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `var a = func(){ return 1 }()`, RunOutput: int64(1), Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a, b = func(){ return 1, 2 }()`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(1), "b": int64(2)}}, + {Script: `var a, b = func(){ return 1, 2 }()`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(1), "b": int64(2)}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestModule(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `module a.b { }`, ParseError: fmt.Errorf("syntax error")}, + {Script: `module a { 1++ }`, RunError: fmt.Errorf("invalid operation")}, + {Script: `module a { }; a.b`, RunError: fmt.Errorf("undefined symbol 'b'")}, + + {Script: `module a { b = 1 }`, RunOutput: nil}, + + {Script: `module a { b = nil }; a.b`, RunOutput: nil}, + {Script: `module a { b = true }; a.b`, RunOutput: true}, + {Script: `module a { b = 1 }; a.b`, RunOutput: int64(1)}, + {Script: `module a { b = 1.5 }; a.b`, RunOutput: float64(1.5)}, + {Script: `module a { b = "a" }; a.b`, RunOutput: "a"}, + {Script: `module a { func b() { return "b"} }; a.b()`, RunOutput: "b"}, + + {Script: `module a { _b = "b"; func c() { return _b} }`, RunOutput: nil}, + {Script: `module a { _b = "b"; func c() { return _b} }; a.c()`, RunOutput: "b"}, + + {Script: `module a { b = 1 }; var c = a; c.b = 2; c.b`, RunOutput: int64(2)}, + + // test module copy + {Script: `module a { b = 1 }; c = a; d = a; a.b = 2; a.b`, RunOutput: int64(2)}, + {Script: `module a { b = 1 }; c = a; d = a; a.b = 2; c.b`, RunOutput: int64(1)}, + {Script: `module a { b = 1 }; c = a; d = a; a.b = 2; d.b`, RunOutput: int64(1)}, + {Script: `module a { b = 1 }; c = a; d = a; c.b = 2; a.b`, RunOutput: int64(1)}, + {Script: `module a { b = 1 }; c = a; d = a; c.b = 2; c.b`, RunOutput: int64(2)}, + {Script: `module a { b = 1 }; c = a; d = a; c.b = 2; d.b`, RunOutput: int64(1)}, + {Script: `module a { b = 1 }; c = a; d = a; d.b = 2; a.b`, RunOutput: int64(1)}, + {Script: `module a { b = 1 }; c = a; d = a; d.b = 2; c.b`, RunOutput: int64(1)}, + {Script: `module a { b = 1 }; c = a; d = a; d.b = 2; d.b`, RunOutput: int64(2)}, + + {Script: `module a { b = 1 }; var c = a; var d = a; a.b = 2; a.b`, RunOutput: int64(2)}, + {Script: `module a { b = 1 }; var c = a; var d = a; a.b = 2; c.b`, RunOutput: int64(1)}, + {Script: `module a { b = 1 }; var c = a; var d = a; a.b = 2; d.b`, RunOutput: int64(1)}, + {Script: `module a { b = 1 }; var c = a; var d = a; c.b = 2; a.b`, RunOutput: int64(1)}, + {Script: `module a { b = 1 }; var c = a; var d = a; c.b = 2; c.b`, RunOutput: int64(2)}, + {Script: `module a { b = 1 }; var c = a; var d = a; c.b = 2; d.b`, RunOutput: int64(1)}, + {Script: `module a { b = 1 }; var c = a; var d = a; d.b = 2; a.b`, RunOutput: int64(1)}, + {Script: `module a { b = 1 }; var c = a; var d = a; d.b = 2; c.b`, RunOutput: int64(1)}, + {Script: `module a { b = 1 }; var c = a; var d = a; d.b = 2; d.b`, RunOutput: int64(2)}, + + // test type scope + {Script: `module b { make(type Duration, a) }; func c() { d = new(time.Duration); return *d }; c()`, Input: map[string]interface{}{"a": time.Duration(0)}, RunError: fmt.Errorf("no namespace called: time")}, + {Script: `module time { make(type Duration, a) }; func c() { d = new(time.Duration); return *d }; c()`, Input: map[string]interface{}{"a": time.Duration(0)}, RunOutput: time.Duration(0)}, + {Script: `module x { module time { make(type Duration, a) } }; func c() { d = new(x.time.Duration); return *d }; c()`, Input: map[string]interface{}{"a": time.Duration(0)}, RunOutput: time.Duration(0)}, + {Script: `module x { module time { make(type Duration, a) } }; func c() { d = new(y.time.Duration); return *d }; c()`, Input: map[string]interface{}{"a": time.Duration(0)}, RunError: fmt.Errorf("no namespace called: y")}, + {Script: `module x { module time { make(type Duration, a) } }; func c() { d = new(x.y.Duration); return *d }; c()`, Input: map[string]interface{}{"a": time.Duration(0)}, RunError: fmt.Errorf("no namespace called: y")}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestNew(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `new(foo)`, RunError: fmt.Errorf("undefined type 'foo'")}, + {Script: `new(nilT)`, Types: map[string]interface{}{"nilT": nil}, RunError: fmt.Errorf("cannot make type nil")}, + + // default + {Script: `a = new(bool); *a`, RunOutput: false}, + {Script: `a = new(int32); *a`, RunOutput: int32(0)}, + {Script: `a = new(int64); *a`, RunOutput: int64(0)}, + {Script: `a = new(float32); *a`, RunOutput: float32(0)}, + {Script: `a = new(float64); *a`, RunOutput: float64(0)}, + {Script: `a = new(string); *a`, RunOutput: ""}, + + // ptr + {Script: `a = new(*string); b = *a; *b`, RunOutput: ""}, + {Script: `a = new(*string); **a`, RunOutput: ""}, + + // slice + {Script: `a = new([]int64); *a`, RunOutput: []int64{}}, + + // map + {Script: `a = new(map[string]int64); *a`, RunOutput: map[string]int64{}}, + + // chan + {Script: `a = new(chan int64); go func(){ (*a) <- 1 }(); <- *a`, RunOutput: int64(1)}, + {Script: `a = new(chan int64); go func(){ *a <- 1 }(); <- *a`, RunOutput: int64(1)}, + + // struct + {Script: `a = new(struct{ A int64 }); *a`, RunOutput: struct{ A int64 }{}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestMake(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `make(map[[]string]int64)`, RunError: fmt.Errorf("reflect.MapOf: invalid key type []string")}, + } + runTests(t, tests, nil, &Options{Debug: false}) + + tests = []Test{ + {Script: `make(struct {})`, ParseError: fmt.Errorf("syntax error")}, + {Script: `make(struct { , })`, ParseError: fmt.Errorf("syntax error")}, + {Script: `make(struct { A map })`, ParseError: fmt.Errorf("syntax error")}, + {Script: `make(struct { , A int64})`, ParseError: fmt.Errorf("syntax error")}, + {Script: `make(struct { A int64, })`, ParseError: fmt.Errorf("syntax error")}, + + {Script: `make(foo)`, RunError: fmt.Errorf("undefined type 'foo'")}, + {Script: `make(a.b)`, Types: map[string]interface{}{"a": true}, RunError: fmt.Errorf("no namespace called: a")}, + {Script: `make(a.b)`, Types: map[string]interface{}{"b": true}, RunError: fmt.Errorf("no namespace called: a")}, + {Script: `make([]int64, 1++)`, RunError: fmt.Errorf("invalid operation")}, + {Script: `make([]int64, 1, 1++)`, RunError: fmt.Errorf("invalid operation")}, + {Script: `make([]int64, 2, 1)`, RunError: fmt.Errorf("make slice len > cap")}, + {Script: `make(map[foo]int64)`, RunError: fmt.Errorf("undefined type 'foo'")}, + {Script: `make(map[int64]foo)`, RunError: fmt.Errorf("undefined type 'foo'")}, + {Script: `make(chan foo)`, RunError: fmt.Errorf("undefined type 'foo'")}, + {Script: `make(chan int64, 1++)`, RunError: fmt.Errorf("invalid operation")}, + {Script: `make(struct { A foo })`, RunError: fmt.Errorf("undefined type 'foo'")}, + + // nill type + {Script: `make(nilT)`, Types: map[string]interface{}{"nilT": nil}, RunError: fmt.Errorf("cannot make type nil")}, + {Script: `make(*nilT)`, Types: map[string]interface{}{"nilT": nil}, RunError: fmt.Errorf("cannot make type nil")}, + {Script: `make([]nilT)`, Types: map[string]interface{}{"nilT": nil}, RunError: fmt.Errorf("cannot make type nil")}, + {Script: `make(map[nilT]string)`, Types: map[string]interface{}{"nilT": nil}, RunError: fmt.Errorf("cannot make type nil")}, + {Script: `make(map[string]nilT)`, Types: map[string]interface{}{"nilT": nil}, RunError: fmt.Errorf("cannot make type nil")}, + {Script: `make(chan nilT)`, Types: map[string]interface{}{"nilT": nil}, RunError: fmt.Errorf("cannot make type nil")}, + {Script: `make(struct { A nilT })`, Types: map[string]interface{}{"nilT": nil}, RunError: fmt.Errorf("cannot make type nil")}, + + // default + {Script: `make(bool)`, RunOutput: false}, + {Script: `make(int32)`, RunOutput: int32(0)}, + {Script: `make(int64)`, RunOutput: int64(0)}, + {Script: `make(float32)`, RunOutput: float32(0)}, + {Script: `make(float64)`, RunOutput: float64(0)}, + {Script: `make(string)`, RunOutput: ""}, + + // ptr + {Script: `a = make(*int64); *a`, RunOutput: int64(0)}, + {Script: `a = make(**int64); **a`, RunOutput: int64(0)}, + {Script: `a = make(***int64); ***a`, RunOutput: int64(0)}, + {Script: `a = make(*[]int64); *a`, RunOutput: []int64{}}, + {Script: `a = make(*map[string]int64); *a`, RunOutput: map[string]int64{}}, + {Script: `a = make(*chan int64); go func(){ (*a) <- 1 }(); <- *a`, RunOutput: int64(1)}, + {Script: `a = make(*chan int64); go func(){ *a <- 1 }(); <- *a`, RunOutput: int64(1)}, + + // slice + {Script: `make([]int64)`, RunOutput: []int64{}}, + {Script: `a = make([]int64, 1); a[0]`, RunOutput: int64(0)}, + {Script: `a = make([]int64, 1, 2); a[0]`, RunOutput: int64(0)}, + {Script: `make([]*int64)`, RunOutput: []*int64{}}, + {Script: `make([][]int64)`, RunOutput: [][]int64{}}, + {Script: `make([]map[string]int64)`, RunOutput: []map[string]int64{}}, + + // map + {Script: `make(map[string]int64)`, RunOutput: map[string]int64{}}, + {Script: `make(map[string]*int64)`, RunOutput: map[string]*int64{}}, + {Script: `make(map[*string]int64)`, RunOutput: map[*string]int64{}}, + {Script: `make(map[*string]*int64)`, RunOutput: map[*string]*int64{}}, + {Script: `make(map[string][]int64)`, RunOutput: map[string][]int64{}}, + {Script: `make(map[string]chan int64)`, RunOutput: map[string]chan int64{}}, + {Script: `make(map[chan string]int64)`, RunOutput: map[chan string]int64{}}, + + // chan + {Script: `a = make(chan int64); go func(){ a <- 1 }(); <- a`, RunOutput: int64(1)}, + {Script: `a = make(chan int64, 1); a <- 1; <- a`, RunOutput: int64(1)}, + {Script: `a = make(chan *int64, 1); b = 1; a <- &b; c = <- a; *c`, RunOutput: int64(1)}, + {Script: `a = make(chan []int64, 1); a <- [1]; <- a`, RunOutput: []int64{1}}, + {Script: `a = make(chan map[string]int64, 1); b = make(map[string]int64); a <- b; <- a`, RunOutput: map[string]int64{}}, + {Script: `a = make(chan int64, 1); b = &a; *b <- 1; <- *b`, RunOutput: int64(1)}, + + // struct + {Script: `make(struct { A int64 })`, RunOutput: struct{ A int64 }{}}, + {Script: `make(struct { A *int64 })`, RunOutput: struct{ A *int64 }{}}, + {Script: `make(struct { A []int64 })`, RunOutput: struct{ A []int64 }{A: []int64{}}}, + {Script: `make(struct { A map[string]int64 })`, RunOutput: struct{ A map[string]int64 }{A: map[string]int64{}}}, + {Script: `a = make(struct { A chan int64 }); go func(){ a.A <- 1 }(); <- a.A`, RunOutput: int64(1)}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestMakeType(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `make(type a, 1++)`, RunError: fmt.Errorf("invalid operation")}, + + {Script: `make(type a, true)`, RunOutput: reflect.TypeOf(true)}, + {Script: `a = make(type a, true)`, RunOutput: reflect.TypeOf(true), Output: map[string]interface{}{"a": reflect.TypeOf(true)}}, + {Script: `make(type a, true); a = make([]a)`, RunOutput: []bool{}, Output: map[string]interface{}{"a": []bool{}}}, + {Script: `make(type a, make([]bool))`, RunOutput: reflect.TypeOf([]bool{})}, + {Script: `make(type a, make([]bool)); a = make(a)`, RunOutput: []bool{}, Output: map[string]interface{}{"a": []bool{}}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestReferencingAndDereference(t *testing.T) { + t.Parallel() + + tests := []Test{ + // TOFIX: + // {Script: `a = 1; b = &a; *b = 2; *b`, RunOutput: int64(2), Output: map[string]interface{}{"a": int64(2)}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestChan(t *testing.T) { + t.Parallel() + + tests := []Test{ + // send on closed channel + {Script: `a = make(chan int64, 2); close(a); a <- 1`, RunError: fmt.Errorf("send on closed channel")}, + } + runTests(t, tests, nil, &Options{Debug: false}) + + tests = []Test{ + {Script: `a = make(chan int64, 2); a <- 1; = <- a`, ParseError: fmt.Errorf("missing expressions on left side of channel operator"), RunError: fmt.Errorf("invalid operation")}, + + {Script: `<- 1++`, RunError: fmt.Errorf("invalid operation")}, + {Script: `1++ <- 1`, RunError: fmt.Errorf("invalid operation")}, + {Script: `1 <- 1++`, RunError: fmt.Errorf("invalid operation")}, + {Script: `a = make(chan int64, 2); a <- 1++`, RunError: fmt.Errorf("invalid operation")}, + {Script: `<- 1`, RunError: fmt.Errorf("receive from non-chan type int64")}, + {Script: `1 <- 1`, RunError: fmt.Errorf("send to non-chan type int64")}, + {Script: `a = make(chan int64, 2); 1 <- a`, RunError: fmt.Errorf("send to non-chan type int64")}, + {Script: `a = make(chan bool, 2); a <- 1`, RunError: fmt.Errorf("cannot use type int64 as type bool to send to chan")}, + {Script: `close(1++)`, RunError: fmt.Errorf("invalid operation")}, + {Script: `close(1)`, RunError: fmt.Errorf("type cannot be int64 for close")}, + + // let channel errors + {Script: `a = <- c`, RunError: fmt.Errorf("undefined symbol 'c'")}, + {Script: `a, b = <- c`, RunError: fmt.Errorf("undefined symbol 'c'")}, + {Script: `a = <- 1++`, RunError: fmt.Errorf("invalid operation")}, + {Script: `a, b = <- 1++`, RunError: fmt.Errorf("invalid operation")}, + {Script: `c = 1; a = <- c`, RunError: fmt.Errorf("receive from non-chan type int64")}, + {Script: `c = 1; a, b = <- c`, RunError: fmt.Errorf("receive from non-chan type int64")}, + {Script: `a = make(chan int64, 2); a <- 1; 1++ = <- a`, RunError: fmt.Errorf("invalid operation")}, + {Script: `a = make(chan int64, 2); a <- 1; 1++, b = <- a`, RunError: fmt.Errorf("invalid operation")}, + + // send to channel + {Script: `a <- nil`, Input: map[string]interface{}{"a": make(chan interface{}, 2)}}, + {Script: `a <- true`, Input: map[string]interface{}{"a": make(chan bool, 2)}}, + {Script: `a <- 1`, Input: map[string]interface{}{"a": make(chan int32, 2)}}, + {Script: `a <- 2`, Input: map[string]interface{}{"a": make(chan int64, 2)}}, + {Script: `a <- 1.5`, Input: map[string]interface{}{"a": make(chan float32, 2)}}, + {Script: `a <- 2.5`, Input: map[string]interface{}{"a": make(chan float64, 2)}}, + {Script: `a <- "b"`, Input: map[string]interface{}{"a": make(chan string, 2)}}, + + {Script: `a = make(chan interface, 2); a <- nil`}, + {Script: `a = make(chan bool, 2); a <- true`}, + {Script: `a = make(chan int32, 2); a <- 1`}, + {Script: `a = make(chan int64, 2); a <- 2`}, + {Script: `a = make(chan float32, 2); a <- 1.5`}, + {Script: `a = make(chan float64, 2); a <- 2.5`}, + {Script: `a = make(chan string, 2); a <- "b"`}, + + // send to channel then receive from channel + {Script: `a <- nil; <- a`, Input: map[string]interface{}{"a": make(chan interface{}, 2)}, RunOutput: nil}, + {Script: `a <- true; <- a`, Input: map[string]interface{}{"a": make(chan bool, 2)}, RunOutput: true}, + {Script: `a <- 1; <- a`, Input: map[string]interface{}{"a": make(chan int32, 2)}, RunOutput: int32(1)}, + {Script: `a <- 2; <- a`, Input: map[string]interface{}{"a": make(chan int64, 2)}, RunOutput: int64(2)}, + {Script: `a <- 1.5; <- a`, Input: map[string]interface{}{"a": make(chan float32, 2)}, RunOutput: float32(1.5)}, + {Script: `a <- 2.5; <- a`, Input: map[string]interface{}{"a": make(chan float64, 2)}, RunOutput: float64(2.5)}, + {Script: `a <- "b"; <- a`, Input: map[string]interface{}{"a": make(chan string, 2)}, RunOutput: "b"}, + + {Script: `a = make(chan interface, 2); a <- nil; <- a`, RunOutput: nil}, + {Script: `a = make(chan bool, 2); a <- true; <- a`, RunOutput: true}, + {Script: `a = make(chan int32, 2); a <- 1; <- a`, RunOutput: int32(1)}, + {Script: `a = make(chan int64, 2); a <- 2; <- a`, RunOutput: int64(2)}, + {Script: `a = make(chan float32, 2); a <- 1.5; <- a`, RunOutput: float32(1.5)}, + {Script: `a = make(chan float64, 2); a <- 2.5; <- a`, RunOutput: float64(2.5)}, + {Script: `a = make(chan string, 2); a <- "b"; <- a`, RunOutput: "b"}, + + // send to channel, receive from channel, then assign to variable + {Script: `a <- nil; b = <- a`, Input: map[string]interface{}{"a": make(chan interface{}, 2)}, RunOutput: nil, Output: map[string]interface{}{"b": nil}}, + {Script: `a <- true; b = <- a`, Input: map[string]interface{}{"a": make(chan bool, 2)}, RunOutput: true, Output: map[string]interface{}{"b": true}}, + {Script: `a <- 1; b = <- a`, Input: map[string]interface{}{"a": make(chan int32, 2)}, RunOutput: int32(1), Output: map[string]interface{}{"b": int32(1)}}, + {Script: `a <- 2; b = <- a`, Input: map[string]interface{}{"a": make(chan int64, 2)}, RunOutput: int64(2), Output: map[string]interface{}{"b": int64(2)}}, + {Script: `a <- 1.5; b = <- a`, Input: map[string]interface{}{"a": make(chan float32, 2)}, RunOutput: float32(1.5), Output: map[string]interface{}{"b": float32(1.5)}}, + {Script: `a <- 2.5; b = <- a`, Input: map[string]interface{}{"a": make(chan float64, 2)}, RunOutput: float64(2.5), Output: map[string]interface{}{"b": float64(2.5)}}, + {Script: `a <- "b"; b = <- a`, Input: map[string]interface{}{"a": make(chan string, 2)}, RunOutput: "b", Output: map[string]interface{}{"b": "b"}}, + + {Script: `a = make(chan interface, 2); a <- nil; b = <- a`, RunOutput: nil, Output: map[string]interface{}{"b": nil}}, + {Script: `a = make(chan bool, 2); a <- true; b = <- a`, RunOutput: true, Output: map[string]interface{}{"b": true}}, + {Script: `a = make(chan int32, 2); a <- 1; b = <- a`, RunOutput: int32(1), Output: map[string]interface{}{"b": int32(1)}}, + {Script: `a = make(chan int64, 2); a <- 2; b = <- a`, RunOutput: int64(2), Output: map[string]interface{}{"b": int64(2)}}, + {Script: `a = make(chan float32, 2); a <- 1.5; b = <- a`, RunOutput: float32(1.5), Output: map[string]interface{}{"b": float32(1.5)}}, + {Script: `a = make(chan float64, 2); a <- 2.5; b = <- a`, RunOutput: float64(2.5), Output: map[string]interface{}{"b": float64(2.5)}}, + {Script: `a = make(chan string, 2); a <- "b"; b = <- a`, RunOutput: "b", Output: map[string]interface{}{"b": "b"}}, + + // receive from closed channel + {Script: `a = make(chan int64, 2); a <- 1; close(a); <- a`, RunOutput: int64(1)}, + {Script: `a = make(chan int64, 2); a <- 1; close(a); <- a; <- a`, RunOutput: nil}, + + // receive & send from same channel + {Script: `a = make(chan int64, 2); a <- 1; a <- <- a`, RunOutput: nil}, + {Script: `a = make(chan int64, 2); a <- 1; a <- <- a; <- a`, RunOutput: int64(1)}, + {Script: `a = make(chan int64, 2); a <- 1; a <- <- a; b = <- a`, RunOutput: int64(1), Output: map[string]interface{}{"b": int64(1)}}, + {Script: `a = make(chan int64, 2); a <- 1; a <- (<- a)`, RunOutput: nil}, + {Script: `a = make(chan int64, 2); a <- 1; a <- (<- a); <- a`, RunOutput: int64(1)}, + {Script: `a = make(chan int64, 2); a <- 1; a <- (<- a); b = <- a`, RunOutput: int64(1), Output: map[string]interface{}{"b": int64(1)}}, + + // 1 then null into a + {Script: `a = make(chan int64, 2); a <- a <- 1`, RunOutput: nil}, + {Script: `a = make(chan int64, 2); a <- a <- 1; <- a`, RunOutput: int64(1)}, + {Script: `a = make(chan int64, 2); a <- a <- 1; <- a; <- a`, RunOutput: int64(0)}, + + // receive & send different channel + {Script: `a = make(chan int64, 2); b = make(chan int64, 2); a <- 1; b <- <- a`, RunOutput: nil}, + {Script: `a = make(chan int64, 2); b = make(chan int64, 2); a <- 1; b <- <- a; <- b`, RunOutput: int64(1)}, + {Script: `a = make(chan int64, 2); b = make(chan int64, 2); a <- 1; b <- <- a; c = <- b`, RunOutput: int64(1), Output: map[string]interface{}{"c": int64(1)}}, + {Script: `a = make(chan int64, 2); b = make(chan int64, 2); a <- 1; b <- (<- a)`, RunOutput: nil}, + {Script: `a = make(chan int64, 2); b = make(chan int64, 2); a <- 1; b <- (<- a); <- b`, RunOutput: int64(1)}, + {Script: `a = make(chan int64, 2); b = make(chan int64, 2); a <- 1; b <- (<- a); c = <- b`, RunOutput: int64(1), Output: map[string]interface{}{"c": int64(1)}}, + + // 1 into a then null into b + {Script: `a = make(chan int64, 2); b = make(chan int64, 2); b <- a <- 1`, RunOutput: nil}, + {Script: `a = make(chan int64, 2); b = make(chan int64, 2); b <- a <- 1; <- a`, RunOutput: int64(1)}, + {Script: `a = make(chan int64, 2); b = make(chan int64, 2); b <- a <- 1; <- a; <- b`, RunOutput: int64(0)}, + + // test ok + {Script: `a = make(chan int64, 2); a <- 1; b, ok = <- a`, RunOutput: int64(1), Output: map[string]interface{}{"b": int64(1), "ok": true}}, + {Script: `a = make(chan int64, 2); a <- 1; b, 1++ = <- a`, RunOutput: int64(1), Output: map[string]interface{}{"b": int64(1)}}, + {Script: `a = make(chan int64, 2); a <- 1; close(a); b, ok = <- a`, RunOutput: int64(1), Output: map[string]interface{}{"b": int64(1), "ok": true}}, + {Script: `a = make(chan int64, 2); a <- 1; close(a); b = <- a; b, ok = <- a`, RunOutput: nil, Output: map[string]interface{}{"b": int64(1), "ok": false}}, + + // test let ++ + {Script: `a = make(chan int64, 2); b = [1, 2, 3, 4]; c = 0; a <- 11; b[c++] = <- a; b[1]`, RunOutput: int64(11)}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestVMDelete(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `delete(1++)`, RunError: fmt.Errorf("invalid operation")}, + {Script: `delete(1)`, RunError: fmt.Errorf("first argument to delete cannot be type int64")}, + {Script: `a = 1; delete("a"); a`, RunError: fmt.Errorf("undefined symbol 'a'")}, + {Script: `a = {"b": "b"}; delete(a)`, RunError: fmt.Errorf("second argument to delete cannot be nil for map")}, + + {Script: `delete("a", 1++)`, RunError: fmt.Errorf("invalid operation")}, + {Script: `b = []; delete(a, b)`, Input: map[string]interface{}{"a": map[int32]interface{}{2: int32(2)}}, RunError: fmt.Errorf("cannot use type int32 as type []interface {} in delete"), Output: map[string]interface{}{"a": map[int32]interface{}{2: int32(2)}}}, + + // test no variable + {Script: `delete("a")`}, + {Script: `delete("a", false)`}, + {Script: `delete("a", true)`}, + {Script: `delete("a", nil)`}, + + // test DeleteGlobal + {Script: `a = 1; func b() { delete("a") }; b()`, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; func b() { delete("a", false) }; b()`, Output: map[string]interface{}{"a": int64(1)}}, + {Script: `a = 1; func b() { delete("a", true) }; b(); a`, RunError: fmt.Errorf("undefined symbol 'a'")}, + {Script: `a = 2; func b() { a = 3; delete("a"); return a }; b()`, RunOutput: int64(3), Output: map[string]interface{}{"a": int64(3)}}, + {Script: `a = 2; func b() { a = 3; delete("a", false); return a }; b()`, RunOutput: int64(3), Output: map[string]interface{}{"a": int64(3)}}, + {Script: `a = 2; func b() { a = 3; delete("a", true); return a }; b()`, RunError: fmt.Errorf("undefined symbol 'a'")}, + {Script: `a = 2; func b() { a = 3; delete("a") }; b(); a`, RunOutput: int64(3), Output: map[string]interface{}{"a": int64(3)}}, + {Script: `a = 2; func b() { a = 3; delete("a", false) }; b(); a`, RunOutput: int64(3), Output: map[string]interface{}{"a": int64(3)}}, + {Script: `a = 2; func b() { a = 3; delete("a", true) }; b(); a`, RunError: fmt.Errorf("undefined symbol 'a'")}, + + // test empty map + {Script: `delete(a, "a")`, Input: map[string]interface{}{"a": testMapEmpty}, Output: map[string]interface{}{"a": testMapEmpty}}, + + // test map + {Script: `a = {"b": "b"}; delete(a, "b")`, Output: map[string]interface{}{"a": map[interface{}]interface{}{}}}, + {Script: `a = {"b": "b"}; delete(a, "b"); a.b`, Output: map[string]interface{}{"a": map[interface{}]interface{}{}}}, + {Script: `a = {"b": "b", "c":"c"}; delete(a, "b")`, Output: map[string]interface{}{"a": map[interface{}]interface{}{"c": "c"}}}, + {Script: `a = {"b": "b", "c":"c"}; delete(a, "b"); a.b`, Output: map[string]interface{}{"a": map[interface{}]interface{}{"c": "c"}}}, + + // test key convert + {Script: `delete(a, 2)`, Input: map[string]interface{}{"a": map[int32]interface{}{2: int32(2)}}, Output: map[string]interface{}{"a": map[int32]interface{}{}}}, + {Script: `delete(a, 2); a[2]`, Input: map[string]interface{}{"a": map[int32]interface{}{2: int32(2)}}, Output: map[string]interface{}{"a": map[int32]interface{}{}}}, + {Script: `delete(a, 2)`, Input: map[string]interface{}{"a": map[int32]interface{}{2: int32(2), 3: int32(3)}}, Output: map[string]interface{}{"a": map[int32]interface{}{3: int32(3)}}}, + {Script: `delete(a, 2); a[2]`, Input: map[string]interface{}{"a": map[int32]interface{}{2: int32(2), 3: int32(3)}}, Output: map[string]interface{}{"a": map[int32]interface{}{3: int32(3)}}}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestComment(t *testing.T) { + t.Parallel() + + tests := []Test{ + {Script: `# 1`}, + {Script: `# 1;`}, + {Script: `# 1 // 2`}, + {Script: `# 1 \n 2`}, + {Script: `# 1 # 2`}, + + {Script: `1# 1`, RunOutput: int64(1)}, + {Script: `1# 1;`, RunOutput: int64(1)}, + {Script: `1# 1 // 2`, RunOutput: int64(1)}, + {Script: `1# 1 \n 2`, RunOutput: int64(1)}, + {Script: `1# 1 # 2`, RunOutput: int64(1)}, + + {Script: `1 +# 1`, RunOutput: int64(1)}, + {Script: `1 +# 1;`, RunOutput: int64(1)}, + {Script: `1 +# 1 // 2`, RunOutput: int64(1)}, + {Script: `1 +# 1 \n 2`, RunOutput: int64(1)}, + {Script: `1 +# 1 # 2`, RunOutput: int64(1)}, + + {Script: `// 1`}, + {Script: `// 1;`}, + {Script: `// 1 // 2`}, + {Script: `// 1 \n 2`}, + {Script: `// 1 # 2`}, + + {Script: `1// 1`, RunOutput: int64(1)}, + {Script: `1// 1;`, RunOutput: int64(1)}, + {Script: `1// 1 // 2`, RunOutput: int64(1)}, + {Script: `1// 1 \n 2`, RunOutput: int64(1)}, + {Script: `1// 1 # 2`, RunOutput: int64(1)}, + + {Script: `1 +// 1`, RunOutput: int64(1)}, + {Script: `1 +// 1;`, RunOutput: int64(1)}, + {Script: `1 +// 1 // 2`, RunOutput: int64(1)}, + {Script: `1 +// 1 \n 2`, RunOutput: int64(1)}, + {Script: `1 +// 1 # 2`, RunOutput: int64(1)}, + + {Script: `/* 1 */`}, + {Script: `/* * 1 */`}, + {Script: `/* 1 * */`}, + {Script: `/** 1 */`}, + {Script: `/*** 1 */`}, + {Script: `/**** 1 */`}, + {Script: `/* 1 **/`}, + {Script: `/* 1 ***/`}, + {Script: `/* 1 ****/`}, + {Script: `/** 1 ****/`}, + {Script: `/*** 1 ****/`}, + {Script: `/**** 1 ****/`}, + + {Script: `1/* 1 */`, RunOutput: int64(1)}, + {Script: `1/* * 1 */`, RunOutput: int64(1)}, + {Script: `1/* 1 * */`, RunOutput: int64(1)}, + {Script: `1/** 1 */`, RunOutput: int64(1)}, + {Script: `1/*** 1 */`, RunOutput: int64(1)}, + {Script: `1/**** 1 */`, RunOutput: int64(1)}, + {Script: `1/* 1 **/`, RunOutput: int64(1)}, + {Script: `1/* 1 ***/`, RunOutput: int64(1)}, + {Script: `1/* 1 ****/`, RunOutput: int64(1)}, + {Script: `1/** 1 ****/`, RunOutput: int64(1)}, + {Script: `1/*** 1 ****/`, RunOutput: int64(1)}, + {Script: `1/**** 1 ****/`, RunOutput: int64(1)}, + + {Script: `/* 1 */1`, RunOutput: int64(1)}, + {Script: `/* * 1 */1`, RunOutput: int64(1)}, + {Script: `/* 1 * */1`, RunOutput: int64(1)}, + {Script: `/** 1 */1`, RunOutput: int64(1)}, + {Script: `/*** 1 */1`, RunOutput: int64(1)}, + {Script: `/**** 1 */1`, RunOutput: int64(1)}, + {Script: `/* 1 **/1`, RunOutput: int64(1)}, + {Script: `/* 1 ***/1`, RunOutput: int64(1)}, + {Script: `/* 1 ****/1`, RunOutput: int64(1)}, + {Script: `/** 1 ****/1`, RunOutput: int64(1)}, + {Script: `/*** 1 ****/1`, RunOutput: int64(1)}, + {Script: `/**** 1 ****/1`, RunOutput: int64(1)}, + + {Script: `1 +/* 1 */`, RunOutput: int64(1)}, + {Script: `1 +/* * 1 */`, RunOutput: int64(1)}, + {Script: `1 +/* 1 * */`, RunOutput: int64(1)}, + {Script: `1 +/** 1 */`, RunOutput: int64(1)}, + {Script: `1 +/*** 1 */`, RunOutput: int64(1)}, + {Script: `1 +/**** 1 */`, RunOutput: int64(1)}, + {Script: `1 +/* 1 **/`, RunOutput: int64(1)}, + {Script: `1 +/* 1 ***/`, RunOutput: int64(1)}, + {Script: `1 +/* 1 ****/`, RunOutput: int64(1)}, + {Script: `1 +/** 1 ****/`, RunOutput: int64(1)}, + {Script: `1 +/*** 1 ****/`, RunOutput: int64(1)}, + {Script: `1 +/**** 1 ****/`, RunOutput: int64(1)}, + } + runTests(t, tests, nil, &Options{Debug: true}) +} + +func TestCancelWithContext(t *testing.T) { + scripts := []string{ + ` +b = 0 +close(waitChan) +for { + b = 1 +} +`, + ` +b = 0 +close(waitChan) +for { + for { + b = 1 + } +} +`, + ` +a = [] +for i = 0; i < 20000; i++ { + a += 1 +} +b = 0 +close(waitChan) +for { + for i in a { + b = i + } +} +`, + ` +a = [] +for i = 0; i < 20000; i++ { + a += 1 +} +b = 0 +close(waitChan) +for i in a { + for j in a { + b = j + } +} +`, + ` +close(waitChan) +b = 0 +for i = 0; true; nil { +} +`, + ` +b = 0 +close(waitChan) +for i = 0; true; nil { + for j = 0; true; nil { + b = 1 + } +} +`, + ` +a = {} +for i = 0; i < 20000; i++ { + a[toString(i)] = 1 +} +b = 0 +close(waitChan) +for { + for i in a { + b = 1 + } +} +`, + ` +a = {} +for i = 0; i < 20000; i++ { + a[toString(i)] = 1 +} +b = 0 +close(waitChan) +for i in a { + for j in a { + b = 1 + } +} +`, + ` +close(waitChan) +<- make(chan string) +`, + ` +a = "" +close(waitChan) +a = <- make(chan string) +`, + ` +for { + a = "" + close(waitChan) + a = <- make(chan string) +} +`, + ` +a = make(chan int) +close(waitChan) +a <- 1 +`, + ` +a = make(chan interface) +close(waitChan) +a <- nil +`, + ` +a = make(chan int64, 1) +close(waitChan) +for v in a { } +`, + ` +close(waitChan) +try { + for { } +} catch { } +`, + } + for _, script := range scripts { + runCancelTestWithContext(t, script) + } +} + +func runCancelTestWithContext(t *testing.T, script string) { + waitChan := make(chan struct{}, 1) + toString := func(value interface{}) string { + return fmt.Sprintf("%v", value) + } + e := env.NewEnv() + err := e.Define("waitChan", waitChan) + if err != nil { + t.Errorf("Define error: %v", err) + } + err = e.Define("toString", toString) + if err != nil { + t.Errorf("Define error: %v", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + <-waitChan + time.Sleep(time.Millisecond) + cancel() + }() + + _, err = ExecuteContext(ctx, e, nil, script) + if err == nil || err.Error() != ErrInterrupt.Error() { + t.Errorf("execute error - received %#v - expected: %#v - script: %v", err, ErrInterrupt, script) + } +} + +func TestContextConcurrency(t *testing.T) { + var waitGroup sync.WaitGroup + e := env.NewEnv() + + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) + waitGroup.Add(100) + for i := 0; i < 100; i++ { + go func() { + _, err := ExecuteContext(ctx, e, nil, "for { }") + if err == nil || err.Error() != ErrInterrupt.Error() { + t.Errorf("execute error - received %#v - expected: %#v", err, ErrInterrupt) + } + waitGroup.Done() + }() + } + cancel() + waitGroup.Wait() + cancel() + + _, err := ExecuteContext(ctx, e, nil, "for { }") + if err == nil || err.Error() != ErrInterrupt.Error() { + t.Errorf("execute error - received %#v - expected: %#v", err, ErrInterrupt) + } + + ctx, cancel = context.WithCancel(context.Background()) + _, err = ExecuteContext(ctx, e, nil, "for i = 0; i < 1000; i++ {}") + if err != nil { + t.Errorf("execute error - received: %v - expected: %v", err, nil) + } + waitGroup.Add(100) + for i := 0; i < 100; i++ { + go func() { + _, err := ExecuteContext(ctx, e, nil, "for i = 0; i < 1000; i++ { }") + if err != nil { + t.Errorf("execute error - received: %v - expected: %v", err, nil) + } + waitGroup.Done() + }() + } + waitGroup.Wait() + + waitGroup.Add(100) + for i := 0; i < 100; i++ { + go func() { + _, err := ExecuteContext(ctx, e, nil, "for { }") + if err == nil || err.Error() != ErrInterrupt.Error() { + t.Errorf("execute error - received %#v - expected: %#v", err, ErrInterrupt) + } + waitGroup.Done() + }() + } + time.Sleep(time.Millisecond) + cancel() + waitGroup.Wait() + + waitGroup.Add(100) + for i := 0; i < 100; i++ { + go func() { + _, err := Execute(e, nil, "for i = 0; i < 1000; i++ { }") + if err != nil { + t.Errorf("execute error - received: %v - expected: %v", err, nil) + } + waitGroup.Done() + }() + } + waitGroup.Wait() +} + +func TestContextFunction(t *testing.T) { + t.Parallel() + + e := env.NewEnv() + script := ` + func myFunc(myVar) { + myVar = 3 + }` + envModule, err := e.NewModule("a") + if err != nil { + t.Fatal("NewModule error:", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + _, err = ExecuteContext(ctx, envModule, nil, script) + if err != nil { + t.Errorf("execute error - received %#v - expected: %#v", err, nil) + } + cancel() + + script = "a.myFunc(2)" + + ctx, cancel = context.WithCancel(context.Background()) + _, err = ExecuteContext(ctx, e, nil, script) + if err != nil { + t.Errorf("execute error - received %#v - expected: %#v", err, nil) + } + cancel() +} + +func TestAssignToInterface(t *testing.T) { + t.Parallel() + + e := env.NewEnv() + X := new(struct { + Stdout io.Writer + }) + err := e.Define("X", X) + if err != nil { + t.Errorf("Define error: %v", err) + } + err = e.Define("a", new(os.File)) + if err != nil { + t.Errorf("Define error: %v", err) + } + _, err = Execute(e, nil, "X.Stdout = a") + if err != nil { + t.Errorf("execute error - received %#v - expected: %#v", err, ErrInterrupt) + } +} + +// TestValueEqual do some basic ValueEqual tests for coverage +func TestValueEqual(t *testing.T) { + t.Parallel() + + result := valueEqual(true, true) + if result != true { + t.Fatal("ValueEqual") + } + result = valueEqual(true, false) + if result != false { + t.Fatal("ValueEqual") + } + result = valueEqual(false, true) + if result != false { + t.Fatal("ValueEqual") + } +} + +// TestUnknownCases tests switch cases that are the unknown cases +func TestUnknownCases(t *testing.T) { + t.Parallel() + + oneLiteral := &ast.LiteralExpr{Literal: reflect.ValueOf(int64(1))} + type ( + BadStmt struct { + ast.StmtImpl + } + BadExpr struct { + ast.ExprImpl + } + BadOperator struct { + ast.OperatorImpl + } + ) + + stmts := []ast.Stmt{ + &BadStmt{}, + &ast.ExprStmt{Expr: &BadExpr{}}, + &ast.ExprStmt{Expr: &ast.OpExpr{Op: &BadOperator{}}}, + &ast.ExprStmt{Expr: &ast.UnaryExpr{Expr: oneLiteral}}, + &ast.ExprStmt{Expr: &ast.OpExpr{Op: &ast.BinaryOperator{LHS: oneLiteral}}}, + &ast.ExprStmt{Expr: &ast.OpExpr{Op: &ast.ComparisonOperator{LHS: oneLiteral, RHS: oneLiteral}}}, + &ast.ExprStmt{Expr: &ast.OpExpr{Op: &ast.AddOperator{LHS: oneLiteral, RHS: oneLiteral}}}, + &ast.ExprStmt{Expr: &ast.OpExpr{Op: &ast.MultiplyOperator{LHS: oneLiteral, RHS: oneLiteral}}}, + } + + for _, stmt := range stmts { + e := env.NewEnv() + _, err := Run(e, nil, stmt) + if err == nil { + t.Errorf("no error - stmt: %#v", stmt) + } else if len(err.Error()) < 9 || err.Error()[:8] != "unknown " { + t.Errorf("err: %v - stmt: %#v", err, stmt) + } + } +} + +func fib(x int) int { + if x < 2 { + return x + } + return fib(x-1) + fib(x-2) +} + +func BenchmarkFibGo(b *testing.B) { + for i := 0; i < b.N; i++ { + fib(29) + } +} + +func BenchmarkFibVM(b *testing.B) { + b.StopTimer() + + e := env.NewEnv() + a, err := e.NewModule("a") + if err != nil { + b.Fatal("NewModule error:", err) + } + + script := ` +fib = func(x) { + if x < 2 { + return x + } + return fib(x-1) + fib(x-2) +}` + + _, err = Execute(a, nil, script) + if err != nil { + b.Fatal("Execute error:", err) + } + + b.ResetTimer() + b.StartTimer() + + for i := 0; i < b.N; i++ { + _, err = Execute(e, nil, "a.fib(29)") + if err != nil { + b.Fatal("Execute error:", err) + } + } +}