Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbee2baf9d | ||
|
|
2819b4167b | ||
|
|
2af660cb1f | ||
|
|
8323068414 | ||
|
|
5b62f9fdb6 | ||
|
|
4f66e3fe6c | ||
|
|
1335b4c64f | ||
|
|
da03c922ca | ||
|
|
9620116c30 | ||
|
|
dd7197f2a2 | ||
|
|
9214806342 | ||
|
|
348e713a8e | ||
|
|
cb81fe41ab | ||
|
|
a876bb3673 | ||
|
|
afa46daccd | ||
|
|
3c00da291e | ||
|
|
c847481184 | ||
|
|
f46ef67180 | ||
|
|
229ddfdae1 | ||
|
|
aa7f0849e3 | ||
|
|
7617b8a090 | ||
|
|
4e06abe002 | ||
|
|
b1a758dd5a | ||
|
|
5bf4daef2d | ||
|
|
e56db3b82e | ||
|
|
d3bbe01d5c | ||
|
|
286d6c6359 | ||
|
|
84424b52bc | ||
|
|
98c2dcd3e5 | ||
|
|
808f0bde9d | ||
|
|
c5c6012947 | ||
|
|
836060c8ad | ||
|
|
7a54353c7b | ||
|
|
b591ba0e78 | ||
|
|
5af51aefe6 | ||
|
|
e7c0f68bab | ||
|
|
bd9a6a4f8a | ||
|
|
3eb2c79fd8 | ||
|
|
4653d87298 | ||
|
|
45d569c215 | ||
|
|
c33caeb573 | ||
|
|
91a55cc4c5 | ||
|
|
05f08d776a | ||
|
|
d2569a85a6 | ||
|
|
772cd68fea | ||
|
|
4af992bccb | ||
|
|
da922ce90b | ||
|
|
7b77b0fa22 | ||
|
|
b7f9a39eff | ||
|
|
d2b25a7426 | ||
|
|
b5bf4ef31a | ||
|
|
a69b9bc2dc | ||
|
|
b84278dcc6 | ||
|
|
32cbcfb412 | ||
|
|
5c73f30f36 | ||
|
|
5cc6fa42e4 | ||
|
|
37fe3422d8 | ||
|
|
d4e25f0259 | ||
|
|
32ff3fb9b0 | ||
|
|
b41fa6eb9d | ||
|
|
c80c605ab9 | ||
|
|
bf843fc09e | ||
|
|
a913a4ea8b | ||
|
|
2f8493c405 | ||
|
|
c7fcfa8534 | ||
|
|
aa012b992e |
15
.github/workflows/go-cross.yml
vendored
15
.github/workflows/go-cross.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.15, 1.16 ]
|
||||
go-version: [ 1.16, 1.17 ]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
|
||||
include:
|
||||
@@ -45,11 +45,16 @@ jobs:
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
# In order:
|
||||
# * Module download cache
|
||||
# * Build cache (Linux)
|
||||
# * Build cache (Mac)
|
||||
# * Build cache (Windows)
|
||||
path: |
|
||||
~/go/pkg/mod # Module download cache
|
||||
~/.cache/go-build # Build cache (Linux)
|
||||
~/Library/Caches/go-build # Build cache (Mac)
|
||||
'%LocalAppData%\go-build' # Build cache (Windows)
|
||||
~/go/pkg/mod
|
||||
~/.cache/go-build
|
||||
~/Library/Caches/go-build
|
||||
%LocalAppData%\go-build
|
||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
||||
|
||||
8
.github/workflows/main.yml
vendored
8
.github/workflows/main.yml
vendored
@@ -7,8 +7,8 @@ on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.16
|
||||
GOLANGCI_LINT_VERSION: v1.36.0
|
||||
GO_VERSION: 1.17
|
||||
GOLANGCI_LINT_VERSION: v1.42.1
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
needs: linting
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.15, 1.16 ]
|
||||
go-version: [ 1.16, 1.17 ]
|
||||
steps:
|
||||
- name: Set up Go ${{ matrix.go-version }}
|
||||
uses: actions/setup-go@v2
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
working-directory: ${{ github.workspace }}/go/src/github.com/traefik/yaegi
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.15, 1.16 ]
|
||||
go-version: [ 1.16, 1.17 ]
|
||||
|
||||
steps:
|
||||
- name: Set up Go ${{ matrix.go-version }}
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
- v[0-9]+.[0-9]+*
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.16
|
||||
GO_VERSION: 1.17
|
||||
|
||||
jobs:
|
||||
|
||||
|
||||
@@ -23,13 +23,16 @@
|
||||
[linters]
|
||||
enable-all = true
|
||||
disable = [
|
||||
"maligned",
|
||||
"golint", # deprecated
|
||||
"scopelint", # deprecated
|
||||
"interfacer", # deprecated
|
||||
"maligned", # deprecated
|
||||
"lll",
|
||||
"gas",
|
||||
"dupl",
|
||||
"prealloc",
|
||||
"scopelint",
|
||||
"gocyclo",
|
||||
"cyclop",
|
||||
"gochecknoinits",
|
||||
"gochecknoglobals",
|
||||
"wsl",
|
||||
@@ -49,6 +52,7 @@
|
||||
"exhaustivestruct",
|
||||
"forbidigo",
|
||||
"ifshort",
|
||||
"forcetypeassert",
|
||||
"errorlint", # TODO: must be reactivate before fixes
|
||||
]
|
||||
|
||||
@@ -59,8 +63,11 @@
|
||||
exclude = []
|
||||
|
||||
[[issues.exclude-rules]]
|
||||
path = "interp/.+_test\\.go"
|
||||
path = ".+_test\\.go"
|
||||
linters = ["goconst"]
|
||||
[[issues.exclude-rules]]
|
||||
path = ".+_test\\.go"
|
||||
text = "var-declaration:"
|
||||
|
||||
[[issues.exclude-rules]]
|
||||
path = "interp/interp.go"
|
||||
@@ -68,6 +75,9 @@
|
||||
[[issues.exclude-rules]]
|
||||
path = "interp/interp.go"
|
||||
text = "`out` can be `io.Writer`"
|
||||
[[issues.exclude-rules]]
|
||||
path = "interp/interp.go"
|
||||
text = "`Panic` should conform to the `XxxError` format"
|
||||
[[issues.exclude-rules]]
|
||||
path = "interp/interp_eval_test.go"
|
||||
linters = ["thelper"]
|
||||
|
||||
@@ -18,7 +18,7 @@ It powers executable Go scripts and plugins, in embedded interpreters or interac
|
||||
* Works everywhere Go works
|
||||
* All Go & runtime resources accessible from script (with control)
|
||||
* Security: `unsafe` and `syscall` packages neither used nor exported by default
|
||||
* Support Go 1.15 and Go 1.16 (the latest 2 major releases)
|
||||
* Support Go 1.16 and Go 1.17 (the latest 2 major releases)
|
||||
|
||||
## Install
|
||||
|
||||
|
||||
10
_test/a29.go
10
_test/a29.go
@@ -6,16 +6,18 @@ const (
|
||||
zero = iota
|
||||
one
|
||||
two
|
||||
three
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := [...]string{
|
||||
zero: "zero",
|
||||
one: "one",
|
||||
two: "two",
|
||||
zero: "zero",
|
||||
one: "one",
|
||||
three: "three",
|
||||
three + 2: "five",
|
||||
}
|
||||
fmt.Printf("%v %T\n", a, a)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// [zero one two] [3]string
|
||||
// [zero one three five] [6]string
|
||||
|
||||
18
_test/alias2.go
Normal file
18
_test/alias2.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (t MyT) Test() string {
|
||||
return "hello"
|
||||
}
|
||||
|
||||
type MyT int
|
||||
|
||||
func main() {
|
||||
t := MyT(1)
|
||||
|
||||
fmt.Println(t.Test())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
22
_test/alias3.go
Normal file
22
_test/alias3.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import "github.com/traefik/yaegi/_test/alias3"
|
||||
|
||||
var globalT *T
|
||||
|
||||
func init() {
|
||||
globalT = &T{A: "test"}
|
||||
}
|
||||
|
||||
type T alias3.T
|
||||
|
||||
func (t *T) PrintT() {
|
||||
(*alias3.T)(t).Print()
|
||||
}
|
||||
|
||||
func main() {
|
||||
globalT.PrintT()
|
||||
}
|
||||
|
||||
// Output:
|
||||
// test
|
||||
9
_test/alias3/alias3.go
Normal file
9
_test/alias3/alias3.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package alias3
|
||||
|
||||
type T struct {
|
||||
A string
|
||||
}
|
||||
|
||||
func (t *T) Print() {
|
||||
println(t.A)
|
||||
}
|
||||
27
_test/alias4.go
Normal file
27
_test/alias4.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type A http.Header
|
||||
|
||||
func (a A) Test1() {
|
||||
fmt.Println("test1")
|
||||
}
|
||||
|
||||
type B A
|
||||
|
||||
func (b B) Test2() {
|
||||
fmt.Println("test2")
|
||||
}
|
||||
|
||||
func main() {
|
||||
b := B{}
|
||||
|
||||
b.Test2()
|
||||
}
|
||||
|
||||
// Output:
|
||||
// test2
|
||||
37
_test/assert2.go
Normal file
37
_test/assert2.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Defined an interface of stringBuilder that compatible with
|
||||
// strings.Builder(go 1.10) and bytes.Buffer(< go 1.10)
|
||||
type stringBuilder interface {
|
||||
WriteRune(r rune) (n int, err error)
|
||||
WriteString(s string) (int, error)
|
||||
Reset()
|
||||
Grow(n int)
|
||||
String() string
|
||||
}
|
||||
|
||||
var builderPool = sync.Pool{New: func() interface{} {
|
||||
return newStringBuilder()
|
||||
}}
|
||||
|
||||
func newStringBuilder() stringBuilder {
|
||||
return &strings.Builder{}
|
||||
}
|
||||
|
||||
func main() {
|
||||
i := builderPool.Get()
|
||||
sb := i.(stringBuilder)
|
||||
_, _ = sb.WriteString("hello")
|
||||
|
||||
println(sb.String())
|
||||
|
||||
builderPool.Put(i)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
@@ -8,4 +8,4 @@ func main() {
|
||||
}
|
||||
|
||||
// Error:
|
||||
// _test/assign15.go:5:26: cannot use type chan<- struct{} as type <-chan struct{} in assignment
|
||||
// _test/assign15.go:5:26: cannot use type chan<- struct {} as type <-chan struct {} in assignment
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
// A test program
|
||||
|
||||
// +build darwin,linux !arm
|
||||
// +build go1.12 !go1.13
|
||||
|
||||
package main
|
||||
|
||||
func main() {
|
||||
println("hello world")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -13,7 +13,7 @@ func client(uri string) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -21,7 +21,7 @@ func client(uri string) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -13,7 +13,7 @@ func client(uri string) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -40,7 +40,7 @@ func client(uri string) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -40,7 +40,7 @@ func client(uri string) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -41,7 +41,7 @@ func client(uri string) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
32
_test/composite18.go
Normal file
32
_test/composite18.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type fn func(string, string) bool
|
||||
|
||||
var funcs = []fn{
|
||||
cmpLessFn,
|
||||
cmpGreaterFn,
|
||||
nil,
|
||||
}
|
||||
|
||||
func cmpLessFn(a string, b string) bool {
|
||||
return a < b
|
||||
}
|
||||
|
||||
func cmpGreaterFn(a string, b string) bool {
|
||||
return a > b
|
||||
}
|
||||
|
||||
func main() {
|
||||
for _, f := range funcs {
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
fmt.Println(f("a", "b"))
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
33
_test/composite19.go
Normal file
33
_test/composite19.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type fn func(string, string) bool
|
||||
|
||||
var funcs = map[string]fn{
|
||||
"less": cmpLessFn,
|
||||
"greater": cmpGreaterFn,
|
||||
"none": nil,
|
||||
}
|
||||
|
||||
func cmpLessFn(a string, b string) bool {
|
||||
return a < b
|
||||
}
|
||||
|
||||
func cmpGreaterFn(a string, b string) bool {
|
||||
return a > b
|
||||
}
|
||||
|
||||
func main() {
|
||||
for _, n := range []string{"less", "greater", "none"} {
|
||||
f := funcs[n]
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
fmt.Println(f("a", "b"))
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
@@ -9,7 +9,7 @@ func main() {
|
||||
const huge = 1 << 100
|
||||
const large = huge >> 38
|
||||
|
||||
fmt.Println(large)
|
||||
fmt.Println(int64(large))
|
||||
}
|
||||
|
||||
// Output:
|
||||
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
const maxLen = int64(int(^uint(0) >> 1))
|
||||
const maxLen = int64(int64(^uint64(0) >> 1))
|
||||
|
||||
func main() {
|
||||
fmt.Println(maxLen)
|
||||
|
||||
28
_test/const26.go
Normal file
28
_test/const26.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func init() {
|
||||
fmt.Println(constString)
|
||||
fmt.Println(const2)
|
||||
fmt.Println(varString)
|
||||
}
|
||||
|
||||
const constString string = "hello"
|
||||
|
||||
const (
|
||||
const1 = iota + 10
|
||||
const2
|
||||
const3
|
||||
)
|
||||
|
||||
var varString string = "test"
|
||||
|
||||
func main() {}
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
// 11
|
||||
// test
|
||||
@@ -2,12 +2,11 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
file, err := ioutil.TempFile("", "yeagibench")
|
||||
file, err := os.CreateTemp("", "yeagibench")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -23,7 +22,7 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(file.Name())
|
||||
b, err := os.ReadFile(file.Name())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
25
_test/fun27.go
Normal file
25
_test/fun27.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
print("test")
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func print(state string) {
|
||||
fmt.Println(state)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// test
|
||||
17
_test/interface52.go
Normal file
17
_test/interface52.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func main() {
|
||||
t := testing.T{}
|
||||
var tb testing.TB
|
||||
tb = &t
|
||||
if tb.TempDir() == "" {
|
||||
println("FAIL")
|
||||
return
|
||||
}
|
||||
println("PASS")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// PASS
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
func main() {
|
||||
r := strings.NewReader("Go is a general-purpose language designed with systems programming in mind.")
|
||||
|
||||
b, err := ioutil.ReadAll(r)
|
||||
b, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -10,4 +10,4 @@ func main() {
|
||||
}
|
||||
|
||||
// Error:
|
||||
// 9:6: cannot use type string as type int in assignment
|
||||
// 9:6: cannot use type untyped string as type int in assignment
|
||||
|
||||
@@ -7,4 +7,5 @@ func main() {
|
||||
println(len(c))
|
||||
}
|
||||
|
||||
// Output: 1
|
||||
// Output:
|
||||
// 1
|
||||
|
||||
46
_test/issue-1187.go
Normal file
46
_test/issue-1187.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type sink interface {
|
||||
io.Writer
|
||||
io.Closer
|
||||
}
|
||||
|
||||
func newSink() sink {
|
||||
// return os.Stdout // Stdout is special in yaegi tests
|
||||
file, err := os.CreateTemp("", "yaegi-test.*")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := newSink()
|
||||
n, err := s.Write([]byte("Hello\n"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var writer io.Writer = s
|
||||
m, err := writer.Write([]byte("Hello\n"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var closer io.Closer = s
|
||||
err = closer.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = os.Remove(s.(*os.File).Name())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
println(m, n)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 6 6
|
||||
31
_test/issue-1189.go
Normal file
31
_test/issue-1189.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
type I interface {
|
||||
Foo() int
|
||||
}
|
||||
|
||||
type S1 struct {
|
||||
i int
|
||||
}
|
||||
|
||||
func (s S1) Foo() int { return s.i }
|
||||
|
||||
type S2 struct{}
|
||||
|
||||
func (s *S2) Foo() int { return 42 }
|
||||
|
||||
func main() {
|
||||
Is := map[string]I{
|
||||
"foo": S1{21},
|
||||
"bar": &S2{},
|
||||
}
|
||||
n := 0
|
||||
for _, s := range Is {
|
||||
n += s.Foo()
|
||||
}
|
||||
bar := "bar"
|
||||
println(n, Is["foo"].Foo(), Is[bar].Foo())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 63 21 42
|
||||
34
_test/issue-1202.go
Normal file
34
_test/issue-1202.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type foobar struct {
|
||||
callback func(string) func()
|
||||
}
|
||||
|
||||
func cb(text string) func() {
|
||||
return func() {
|
||||
fmt.Println(text)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// These ways of invoking it all work...
|
||||
cb("Hi from inline callback!")()
|
||||
|
||||
asVarTest1 := cb("Hi from asVarTest1 callback!")
|
||||
asVarTest1()
|
||||
|
||||
asVarTest2 := cb
|
||||
asVarTest2("Hi from asVarTest2 callback!")()
|
||||
|
||||
// But inside a struct panics in yaegi...
|
||||
asStructField := &foobar{callback: cb}
|
||||
asStructField.callback("Hi from struct field callback!")() // <--- panics here
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Hi from inline callback!
|
||||
// Hi from asVarTest1 callback!
|
||||
// Hi from asVarTest2 callback!
|
||||
// Hi from struct field callback!
|
||||
25
_test/issue-1205.go
Normal file
25
_test/issue-1205.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
type Option interface {
|
||||
apply()
|
||||
}
|
||||
|
||||
func f(opts ...Option) {
|
||||
for _, opt := range opts {
|
||||
opt.apply()
|
||||
}
|
||||
}
|
||||
|
||||
type T struct{}
|
||||
|
||||
func (t *T) apply() { println("in apply") }
|
||||
|
||||
func main() {
|
||||
opt := []Option{&T{}}
|
||||
f(opt[0]) // works
|
||||
f(opt...) // fails
|
||||
}
|
||||
|
||||
// Output:
|
||||
// in apply
|
||||
// in apply
|
||||
23
_test/issue-1208.go
Normal file
23
_test/issue-1208.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
type Enabler interface {
|
||||
Enabled() bool
|
||||
}
|
||||
|
||||
type Logger struct {
|
||||
core Enabler
|
||||
}
|
||||
|
||||
func (log *Logger) GetCore() Enabler { return log.core }
|
||||
|
||||
type T struct{}
|
||||
|
||||
func (t *T) Enabled() bool { return true }
|
||||
|
||||
func main() {
|
||||
base := &Logger{&T{}}
|
||||
println(base.GetCore().Enabled())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// true
|
||||
67
_test/issue-1260.go
Normal file
67
_test/issue-1260.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type WriteSyncer interface {
|
||||
io.Writer
|
||||
Sync() error
|
||||
}
|
||||
|
||||
type Sink interface {
|
||||
WriteSyncer
|
||||
io.Closer
|
||||
}
|
||||
|
||||
func newFileSink(path string) (Sink, error) {
|
||||
return os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
|
||||
}
|
||||
|
||||
type Sink1 struct{ name string }
|
||||
|
||||
func (s Sink1) Write(b []byte) (int, error) { println("in Write"); return 0, nil }
|
||||
func (s Sink1) Sync() error { println("in Sync"); return nil }
|
||||
func (s Sink1) Close() error { println("in Close", s.name); return nil }
|
||||
func newS1(name string) Sink { return Sink1{name} }
|
||||
func newS1p(name string) Sink { return &Sink1{name} }
|
||||
|
||||
type Sink2 struct{ name string }
|
||||
|
||||
func (s *Sink2) Write(b []byte) (int, error) { println("in Write"); return 0, nil }
|
||||
func (s *Sink2) Sync() error { println("in Sync"); return nil }
|
||||
func (s *Sink2) Close() error { println("in Close", s.name); return nil }
|
||||
func newS2(name string) Sink { return Sink1{name} }
|
||||
|
||||
func main() {
|
||||
tmpfile, err := os.CreateTemp("", "xxx")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.Remove(tmpfile.Name())
|
||||
closers := []io.Closer{}
|
||||
sink, err := newFileSink(tmpfile.Name())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
closers = append(closers, sink)
|
||||
|
||||
s1p := newS1p("ptr")
|
||||
s1 := newS1("struct")
|
||||
s2 := newS2("ptr2")
|
||||
closers = append(closers, s1p, s1, s2)
|
||||
for _, closer := range closers {
|
||||
fmt.Println(closer.Close())
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// <nil>
|
||||
// in Close ptr
|
||||
// <nil>
|
||||
// in Close struct
|
||||
// <nil>
|
||||
// in Close ptr2
|
||||
// <nil>
|
||||
24
_test/issue-1276.go
Normal file
24
_test/issue-1276.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
)
|
||||
|
||||
type customFlag struct{}
|
||||
|
||||
func (cf customFlag) String() string {
|
||||
return "custom flag"
|
||||
}
|
||||
|
||||
func (cf customFlag) Set(string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Var(customFlag{}, "cf", "custom flag")
|
||||
flag.Parse()
|
||||
println("Hello, playground")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Hello, playground
|
||||
30
_test/issue-1280.go
Normal file
30
_test/issue-1280.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type DBReader interface {
|
||||
io.ReadCloser
|
||||
io.ReaderAt
|
||||
}
|
||||
|
||||
type DB struct {
|
||||
f DBReader
|
||||
}
|
||||
|
||||
func main() {
|
||||
f, err := os.Open("/dev/null")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
d := &DB{f}
|
||||
data := make([]byte, 1)
|
||||
_, _ = d.f.ReadAt(data, 0)
|
||||
println("bye")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// bye
|
||||
25
_test/issue-1285.go
Normal file
25
_test/issue-1285.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
type (
|
||||
T1 struct{ Path [12]int8 }
|
||||
T2 struct{ Path *[12]int8 }
|
||||
)
|
||||
|
||||
var (
|
||||
t11 = &T1{}
|
||||
t21 = &T2{}
|
||||
)
|
||||
|
||||
func main() {
|
||||
b := [12]byte{}
|
||||
t12 := &T1{}
|
||||
t22 := &T2{}
|
||||
b11 := (*[len(t11.Path)]byte)(&b)
|
||||
b12 := (*[len(t12.Path)]byte)(&b)
|
||||
b21 := (*[len(t21.Path)]byte)(&b)
|
||||
b22 := (*[len(t22.Path)]byte)(&b)
|
||||
println(len(b11), len(b12), len(b21), len(b22))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 12 12 12 12
|
||||
10
_test/issue-1288.go
Normal file
10
_test/issue-1288.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
println("Hi")
|
||||
goto done
|
||||
done:
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Hi
|
||||
20
_test/issue-1300.go
Normal file
20
_test/issue-1300.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
const buflen = 512
|
||||
|
||||
type T struct {
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func f(t *T) { *t = T{buf: make([]byte, 0, buflen)} }
|
||||
|
||||
func main() {
|
||||
s := T{}
|
||||
println(cap(s.buf))
|
||||
f(&s)
|
||||
println(cap(s.buf))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 0
|
||||
// 512
|
||||
16
_test/issue-1304.go
Normal file
16
_test/issue-1304.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
type Node struct {
|
||||
Name string
|
||||
Alias *Node
|
||||
Child []*Node
|
||||
}
|
||||
|
||||
func main() {
|
||||
n := &Node{Name: "parent"}
|
||||
n.Child = append(n.Child, &Node{Name: "child"})
|
||||
println(n.Name, n.Child[0].Name)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// parent child
|
||||
15
_test/issue-1306.go
Normal file
15
_test/issue-1306.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func check() (result bool, err error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
result, error := check()
|
||||
fmt.Println(result, error)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// true <nil>
|
||||
27
_test/issue-1308.go
Normal file
27
_test/issue-1308.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type test struct {
|
||||
v interface{}
|
||||
s string
|
||||
}
|
||||
|
||||
type T struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func main() {
|
||||
t := []test{
|
||||
{
|
||||
v: []interface{}{
|
||||
T{"hello"},
|
||||
},
|
||||
s: "world",
|
||||
},
|
||||
}
|
||||
fmt.Println(t)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// [{[{hello}] world}]
|
||||
19
_test/issue-1311.go
Normal file
19
_test/issue-1311.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
type T struct {
|
||||
v interface{}
|
||||
}
|
||||
|
||||
func f() (ret int64, err error) {
|
||||
ret += 2
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
t := &T{}
|
||||
t.v, _ = f()
|
||||
println(t.v.(int64))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 2
|
||||
32
_test/issue-1315.go
Normal file
32
_test/issue-1315.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
type Intf interface {
|
||||
M()
|
||||
}
|
||||
|
||||
type T struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (t *T) M() { println("in M") }
|
||||
|
||||
func f(i interface{}) {
|
||||
switch j := i.(type) {
|
||||
case Intf:
|
||||
j.M()
|
||||
default:
|
||||
println("default")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var i Intf
|
||||
var k interface{} = 1
|
||||
i = &T{"hello"}
|
||||
f(i)
|
||||
f(k)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// in M
|
||||
// default
|
||||
29
_test/issue-1320.go
Normal file
29
_test/issue-1320.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
type Pooler interface {
|
||||
Get() string
|
||||
}
|
||||
|
||||
type baseClient struct {
|
||||
connPool Pooler
|
||||
}
|
||||
|
||||
type connPool struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (c *connPool) Get() string { return c.name }
|
||||
|
||||
func newBaseClient(i int, p Pooler) *baseClient {
|
||||
return &baseClient{connPool: p}
|
||||
}
|
||||
|
||||
func newConnPool() *connPool { return &connPool{name: "connPool"} }
|
||||
|
||||
func main() {
|
||||
b := newBaseClient(0, newConnPool())
|
||||
println(b.connPool.(*connPool).name)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// connPool
|
||||
42
_test/issue-1326.go
Normal file
42
_test/issue-1326.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
type Option interface {
|
||||
apply(*T)
|
||||
}
|
||||
|
||||
type T struct {
|
||||
s string
|
||||
}
|
||||
|
||||
type opt struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (o *opt) apply(t *T) {
|
||||
println(o.name)
|
||||
}
|
||||
|
||||
func BuildOptions() []Option {
|
||||
return []Option{
|
||||
&opt{"opt1"},
|
||||
&opt{"opt2"},
|
||||
}
|
||||
}
|
||||
|
||||
func NewT(name string, options ...Option) *T {
|
||||
t := &T{name}
|
||||
for _, opt := range options {
|
||||
opt.apply(t)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func main() {
|
||||
t := NewT("hello", BuildOptions()...)
|
||||
println(t.s)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// opt1
|
||||
// opt2
|
||||
// hello
|
||||
16
_test/issue-1328.go
Normal file
16
_test/issue-1328.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func main() {
|
||||
script := "hello"
|
||||
sumRaw := sha1.Sum([]byte(script))
|
||||
sum := hex.EncodeToString(sumRaw[:])
|
||||
println(sum)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
|
||||
42
_test/issue-1330.go
Normal file
42
_test/issue-1330.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
type wrappedConn struct {
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func main() {
|
||||
_, err := net.Listen("tcp", "127.0.0.1:49153")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
dialer := &net.Dialer{
|
||||
LocalAddr: &net.TCPAddr{
|
||||
IP: net.ParseIP("127.0.0.1"),
|
||||
Port: 0,
|
||||
},
|
||||
}
|
||||
|
||||
conn, err := dialer.Dial("tcp", "127.0.0.1:49153")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
t := &wrappedConn{conn}
|
||||
var w io.Writer = t
|
||||
if n, err := w.Write([]byte("hello")); err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
fmt.Println(n)
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 5
|
||||
37
_test/issue-1333.go
Normal file
37
_test/issue-1333.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
)
|
||||
|
||||
func mock(name string) http.HandlerFunc {
|
||||
return func(rw http.ResponseWriter, req *http.Request) {
|
||||
fmt.Fprint(rw, "Hello ", name)
|
||||
}
|
||||
}
|
||||
|
||||
func client(uri string) {
|
||||
resp, err := http.Get(uri)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(body))
|
||||
}
|
||||
|
||||
func main() {
|
||||
mux := http.NewServeMux()
|
||||
server := httptest.NewServer(mux)
|
||||
defer server.Close()
|
||||
mux.Handle("/", mock("foo"))
|
||||
client(server.URL)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Hello foo
|
||||
@@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
@@ -36,7 +35,7 @@ type pipe struct {
|
||||
|
||||
func newReadAutoCloser(r io.Reader) readAutoCloser {
|
||||
if _, ok := r.(io.Closer); !ok {
|
||||
return readAutoCloser{ioutil.NopCloser(r)}
|
||||
return readAutoCloser{io.NopCloser(r)}
|
||||
}
|
||||
return readAutoCloser{r.(io.ReadCloser)}
|
||||
}
|
||||
@@ -44,7 +43,7 @@ func newReadAutoCloser(r io.Reader) readAutoCloser {
|
||||
func main() {
|
||||
p := &pipe{}
|
||||
p.Reader = newReadAutoCloser(strings.NewReader("test"))
|
||||
b, err := ioutil.ReadAll(p.Reader)
|
||||
b, err := io.ReadAll(p.Reader)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
14
_test/method38.go
Normal file
14
_test/method38.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/traefik/yaegi/_test/method38"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(method38.Get())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// &{[] {<nil>}}
|
||||
19
_test/method38/a.go
Normal file
19
_test/method38/a.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package method38
|
||||
|
||||
import "sync"
|
||||
|
||||
func NewPool() Pool { return Pool{} }
|
||||
|
||||
type Buffer struct {
|
||||
bs []byte
|
||||
pool Pool
|
||||
}
|
||||
|
||||
type Pool struct {
|
||||
p *sync.Pool
|
||||
}
|
||||
|
||||
var (
|
||||
_pool = NewPool()
|
||||
Get = _pool.Get
|
||||
)
|
||||
3
_test/method38/b.go
Normal file
3
_test/method38/b.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package method38
|
||||
|
||||
func (p Pool) Get() *Buffer { return &Buffer{} }
|
||||
32
_test/method39.go
Normal file
32
_test/method39.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/traefik/yaegi/_test/method38"
|
||||
)
|
||||
|
||||
func NewPool() Pool { return Pool{} }
|
||||
|
||||
type Buffer struct {
|
||||
bs []byte
|
||||
pool Pool
|
||||
}
|
||||
|
||||
type Pool struct {
|
||||
p *sync.Pool
|
||||
}
|
||||
|
||||
var (
|
||||
_pool = NewPool()
|
||||
Get = _pool.Get
|
||||
)
|
||||
|
||||
|
||||
func main() {
|
||||
fmt.Println(Get())
|
||||
}
|
||||
|
||||
// Error:
|
||||
// 17:11: undefined selector Get
|
||||
@@ -7,4 +7,4 @@ func main() {
|
||||
}
|
||||
|
||||
// Error:
|
||||
// 5:2: invalid operation: mismatched types int and float64
|
||||
// 5:2: invalid operation: mismatched types int and untyped float
|
||||
|
||||
@@ -2,11 +2,11 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
_, err := ioutil.ReadFile("__NotExisting__")
|
||||
_, err := os.ReadFile("__NotExisting__")
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
22
_test/rune2.go
Normal file
22
_test/rune2.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
const majorVersion = '2'
|
||||
|
||||
type hashed struct {
|
||||
major byte
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println(majorVersion)
|
||||
|
||||
p := new(hashed)
|
||||
p.major = majorVersion
|
||||
|
||||
fmt.Println(p)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 50
|
||||
// &{50}
|
||||
@@ -9,10 +9,20 @@ func test(time string, t time.Time) string {
|
||||
return time
|
||||
}
|
||||
|
||||
var zero = time.Time{}
|
||||
|
||||
func test2(time string) time.Time {
|
||||
return zero
|
||||
}
|
||||
|
||||
func main() {
|
||||
str := test("test", time.Now())
|
||||
fmt.Println(str)
|
||||
|
||||
str2 := test2("test2")
|
||||
fmt.Println(str2)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// test
|
||||
// 0001-01-01 00:00:00 +0000 UTC
|
||||
|
||||
@@ -8,6 +8,7 @@ type A struct {
|
||||
}
|
||||
|
||||
type D struct {
|
||||
F *A
|
||||
E *A
|
||||
}
|
||||
|
||||
|
||||
20
_test/struct60.go
Normal file
20
_test/struct60.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type data struct {
|
||||
S string
|
||||
}
|
||||
|
||||
func render(v interface{}) {
|
||||
fmt.Println(v)
|
||||
}
|
||||
|
||||
func main() {
|
||||
render(data{})
|
||||
}
|
||||
|
||||
// Output:
|
||||
// {}
|
||||
22
_test/struct61.go
Normal file
22
_test/struct61.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type A struct {
|
||||
B string
|
||||
D
|
||||
}
|
||||
|
||||
type D struct {
|
||||
*A
|
||||
E *A
|
||||
}
|
||||
|
||||
func main() {
|
||||
a := &A{B: "b"}
|
||||
a.D = D{E: a}
|
||||
fmt.Println(a.D.E.B)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// b
|
||||
11
_test/struct62.go
Normal file
11
_test/struct62.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
type A struct{ *A }
|
||||
v := &A{}
|
||||
v.A = v
|
||||
println("v.A.A = v", v.A.A == v)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// v.A.A = v true
|
||||
17
_test/type27.go
Normal file
17
_test/type27.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Foo = int
|
||||
|
||||
func (f Foo) Bar() int {
|
||||
return f * f
|
||||
}
|
||||
|
||||
func main() {
|
||||
x := Foo(1)
|
||||
fmt.Println(x.Bar())
|
||||
}
|
||||
|
||||
// Error:
|
||||
// 7:1: cannot define new methods on non-local type int
|
||||
17
_test/type28.go
Normal file
17
_test/type28.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Foo = int
|
||||
|
||||
func (f *Foo) Bar() int {
|
||||
return *f * *f
|
||||
}
|
||||
|
||||
func main() {
|
||||
x := Foo(1)
|
||||
fmt.Println(x.Bar())
|
||||
}
|
||||
|
||||
// Error:
|
||||
// 7:1: cannot define new methods on non-local type int
|
||||
12
_test/type29.go
Normal file
12
_test/type29.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
var Foo int
|
||||
|
||||
func (f Foo) Bar() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func main() {}
|
||||
|
||||
// Error:
|
||||
// 5:1: cannot define new methods on non-local type int
|
||||
12
_test/type30.go
Normal file
12
_test/type30.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
var Foo *int
|
||||
|
||||
func (f Foo) Bar() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func main() {}
|
||||
|
||||
// Error:
|
||||
// 5:1: cannot define new methods on non-local type int
|
||||
17
_test/type31.go
Normal file
17
_test/type31.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (f Foo) Bar() int {
|
||||
return f * f
|
||||
}
|
||||
|
||||
type Foo = int
|
||||
|
||||
func main() {
|
||||
x := Foo(1)
|
||||
fmt.Println(x.Bar())
|
||||
}
|
||||
|
||||
// Error:
|
||||
// 5:1: cannot define new methods on non-local type int
|
||||
17
_test/type32.go
Normal file
17
_test/type32.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (f *Foo) Bar() int {
|
||||
return *f * *f
|
||||
}
|
||||
|
||||
type Foo = int
|
||||
|
||||
func main() {
|
||||
x := Foo(1)
|
||||
fmt.Println(x.Bar())
|
||||
}
|
||||
|
||||
// Error:
|
||||
// 5:1: cannot define new methods on non-local type int
|
||||
11
_test/type33.go
Normal file
11
_test/type33.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package main
|
||||
|
||||
func (f *Foo) Bar() int {
|
||||
return *f * *f
|
||||
}
|
||||
|
||||
func main() {
|
||||
}
|
||||
|
||||
// Error:
|
||||
// 3:1: undefined: Foo
|
||||
@@ -1,11 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const SSize = 16
|
||||
const (
|
||||
SSize = 16
|
||||
WSize = bits.UintSize / 8
|
||||
)
|
||||
|
||||
type S struct {
|
||||
X int
|
||||
@@ -13,14 +16,19 @@ type S struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
bigEndian := (*(*[2]uint8)(unsafe.Pointer(&[]uint16{1}[0])))[0] == 0
|
||||
var sBuf [SSize]byte
|
||||
s := (*S)(unsafe.Pointer(&sBuf[0]))
|
||||
|
||||
s.X = 2
|
||||
s.Y = 4
|
||||
|
||||
fmt.Println(sBuf)
|
||||
if bigEndian {
|
||||
println(sBuf[0+WSize-1], sBuf[WSize+WSize-1])
|
||||
} else {
|
||||
println(sBuf[0], sBuf[WSize])
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// [2 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0]
|
||||
// 2 4
|
||||
|
||||
@@ -2,10 +2,11 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const SSize = 24
|
||||
const WSize = bits.UintSize / 8
|
||||
|
||||
type S struct {
|
||||
X int
|
||||
@@ -20,7 +21,8 @@ func main() {
|
||||
{X: 3},
|
||||
}
|
||||
addr := unsafe.Pointer(&arr[0])
|
||||
s := *(*S)(unsafe.Pointer(uintptr(addr) + SSize*2))
|
||||
// s := *(*S)(unsafe.Pointer(uintptr(addr) + SSize*2))
|
||||
s := *(*S)(unsafe.Pointer(uintptr(addr) + WSize*6))
|
||||
|
||||
fmt.Println(s.X)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const WSize = bits.UintSize / 8
|
||||
|
||||
type S struct {
|
||||
X int
|
||||
Y int
|
||||
@@ -13,12 +15,12 @@ type S struct {
|
||||
|
||||
func main() {
|
||||
x := S{}
|
||||
size := unsafe.Sizeof(x)
|
||||
align := unsafe.Alignof(x.Y)
|
||||
offset := unsafe.Offsetof(x.Z)
|
||||
size := unsafe.Sizeof(x) / WSize
|
||||
align := unsafe.Alignof(x.Y) / WSize
|
||||
offset := unsafe.Offsetof(x.Z) / WSize
|
||||
|
||||
fmt.Println(size, align, offset)
|
||||
println(size, align, offset)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 24 8 16
|
||||
// 3 1 2
|
||||
|
||||
23
_test/unsafe6.go
Normal file
23
_test/unsafe6.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type S struct {
|
||||
X int
|
||||
Y int
|
||||
Z int
|
||||
}
|
||||
|
||||
func main() {
|
||||
x := S{Z: 5}
|
||||
ptr := unsafe.Pointer(&x)
|
||||
offset := int(unsafe.Offsetof(x.Z))
|
||||
p := unsafe.Add(ptr, offset)
|
||||
|
||||
i := *(*int)(p)
|
||||
|
||||
fmt.Println(i)
|
||||
}
|
||||
20
_test/unsafe7.go
Normal file
20
_test/unsafe7.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type S struct {
|
||||
X int
|
||||
Y int
|
||||
Z int
|
||||
}
|
||||
|
||||
func main() {
|
||||
x := [2]S{{Z: 5}, {Z: 10}}
|
||||
|
||||
s := unsafe.Slice(&x[0], 2)
|
||||
|
||||
fmt.Println(s)
|
||||
}
|
||||
@@ -18,12 +18,14 @@ func extractCmd(arg []string) error {
|
||||
var name string
|
||||
var exclude string
|
||||
var include string
|
||||
var tag string
|
||||
|
||||
eflag := flag.NewFlagSet("run", flag.ContinueOnError)
|
||||
eflag.StringVar(&licensePath, "license", "", "path to a LICENSE file")
|
||||
eflag.StringVar(&name, "name", "", "the namespace for the extracted symbols")
|
||||
eflag.StringVar(&exclude, "exclude", "", "comma separated list of regexp matching symbols to exclude")
|
||||
eflag.StringVar(&include, "include", "", "comma separated list of regexp matching symbols to include")
|
||||
eflag.StringVar(&tag, "tag", "", "comma separated list of build tags to be added to the created package")
|
||||
eflag.Usage = func() {
|
||||
fmt.Println("Usage: yaegi extract [options] packages...")
|
||||
fmt.Println("Options:")
|
||||
@@ -56,6 +58,9 @@ func extractCmd(arg []string) error {
|
||||
Dest: name,
|
||||
License: license,
|
||||
}
|
||||
if tag != "" {
|
||||
ext.Tag = strings.Split(tag, ",")
|
||||
}
|
||||
|
||||
if exclude != "" {
|
||||
ext.Exclude = strings.Split(exclude, ",")
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@@ -47,7 +46,12 @@ func run(arg []string) error {
|
||||
}
|
||||
args := rflag.Args()
|
||||
|
||||
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
|
||||
i := interp.New(interp.Options{
|
||||
GoPath: build.Default.GOPATH,
|
||||
BuildTags: strings.Split(tags, ","),
|
||||
Env: os.Environ(),
|
||||
Unrestricted: useUnrestricted,
|
||||
})
|
||||
if err := i.Use(stdlib.Symbols); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -130,7 +134,7 @@ func isFile(path string) bool {
|
||||
}
|
||||
|
||||
func runFile(i *interp.Interpreter, path string, noAutoImport bool) error {
|
||||
b, err := ioutil.ReadFile(path)
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -116,7 +116,12 @@ func test(arg []string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
|
||||
i := interp.New(interp.Options{
|
||||
GoPath: build.Default.GOPATH,
|
||||
BuildTags: strings.Split(tags, ","),
|
||||
Env: os.Environ(),
|
||||
Unrestricted: useUnrestricted,
|
||||
})
|
||||
if err := i.Use(stdlib.Symbols); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -37,7 +37,7 @@ func applyCIMultiplier(timeout time.Duration) time.Duration {
|
||||
}
|
||||
|
||||
func TestYaegiCmdCancel(t *testing.T) {
|
||||
tmp, err := ioutil.TempDir("", "yaegi-")
|
||||
tmp, err := os.MkdirTemp("", "yaegi-")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create tmp directory: %v", err)
|
||||
}
|
||||
@@ -49,7 +49,15 @@ func TestYaegiCmdCancel(t *testing.T) {
|
||||
}()
|
||||
|
||||
yaegi := filepath.Join(tmp, "yaegi")
|
||||
build := exec.Command("go", "build", "-race", "-o", yaegi, ".")
|
||||
|
||||
args := []string{"build"}
|
||||
if raceDetectorSupported(runtime.GOOS, runtime.GOARCH) {
|
||||
args = append(args, "-race")
|
||||
}
|
||||
args = append(args, "-o", yaegi, ".")
|
||||
|
||||
build := exec.Command("go", args...)
|
||||
|
||||
out, err := build.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build yaegi command: %v: %s", err, out)
|
||||
@@ -115,3 +123,16 @@ func TestYaegiCmdCancel(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func raceDetectorSupported(goos, goarch string) bool {
|
||||
switch goos {
|
||||
case "linux":
|
||||
return goarch == "amd64" || goarch == "ppc64le" || goarch == "arm64"
|
||||
case "darwin":
|
||||
return goarch == "amd64" || goarch == "arm64"
|
||||
case "freebsd", "netbsd", "openbsd", "windows":
|
||||
return goarch == "amd64"
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
67
example/fs/fs_test.go
Normal file
67
example/fs/fs_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package fs1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
// only available from 1.16.
|
||||
"testing/fstest"
|
||||
|
||||
"github.com/traefik/yaegi/interp"
|
||||
"github.com/traefik/yaegi/stdlib"
|
||||
)
|
||||
|
||||
var testFilesystem = fstest.MapFS{
|
||||
"main.go": &fstest.MapFile{
|
||||
Data: []byte(`package main
|
||||
|
||||
import (
|
||||
"foo/bar"
|
||||
"./localfoo"
|
||||
)
|
||||
|
||||
func main() {
|
||||
bar.PrintSomething()
|
||||
localfoo.PrintSomethingElse()
|
||||
}
|
||||
`),
|
||||
},
|
||||
"_pkg/src/foo/bar/bar.go": &fstest.MapFile{
|
||||
Data: []byte(`package bar
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func PrintSomething() {
|
||||
fmt.Println("I am a virtual filesystem printing something from _pkg/src/foo/bar/bar.go!")
|
||||
}
|
||||
`),
|
||||
},
|
||||
"localfoo/foo.go": &fstest.MapFile{
|
||||
Data: []byte(`package localfoo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func PrintSomethingElse() {
|
||||
fmt.Println("I am virtual filesystem printing else from localfoo/foo.go!")
|
||||
}
|
||||
`),
|
||||
},
|
||||
}
|
||||
|
||||
func TestFilesystemMapFS(t *testing.T) {
|
||||
i := interp.New(interp.Options{
|
||||
GoPath: "./_pkg",
|
||||
SourcecodeFilesystem: testFilesystem,
|
||||
})
|
||||
if err := i.Use(stdlib.Symbols); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := i.EvalPath(`main.go`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,14 @@ func init() {
|
||||
{{end}}
|
||||
}
|
||||
{{range $m := $value.Method -}}
|
||||
func (W {{$value.Name}}) {{$m.Name}}{{$m.Param}} {{$m.Result}} { {{$m.Ret}} W.W{{$m.Name}}{{$m.Arg}} }
|
||||
func (W {{$value.Name}}) {{$m.Name}}{{$m.Param}} {{$m.Result}} {
|
||||
{{- if eq $m.Name "String"}}
|
||||
if W.WString == nil {
|
||||
return ""
|
||||
}
|
||||
{{end -}}
|
||||
{{$m.Ret}} W.W{{$m.Name}}{{$m.Arg}}
|
||||
}
|
||||
{{end}}
|
||||
{{end}}
|
||||
`
|
||||
@@ -270,6 +277,15 @@ func (e *Extractor) genContent(importPath string, p *types.Package) ([]byte, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range e.Tag {
|
||||
if len(t) != 0 {
|
||||
buildTags += "," + t
|
||||
}
|
||||
}
|
||||
if len(buildTags) != 0 && buildTags[0] == ',' {
|
||||
buildTags = buildTags[1:]
|
||||
}
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
data := map[string]interface{}{
|
||||
"Dest": e.Dest,
|
||||
@@ -336,6 +352,7 @@ type Extractor struct {
|
||||
License string // License text to be included in the created package, optional.
|
||||
Exclude []string // Comma separated list of regexp matching symbols to exclude.
|
||||
Include []string // Comma separated list of regexp matching symbols to include.
|
||||
Tag []string // Comma separated of build tags to be added to the created package.
|
||||
}
|
||||
|
||||
// importPath checks whether pkgIdent is an existing directory relative to
|
||||
@@ -446,7 +463,7 @@ func GetMinor(part string) string {
|
||||
return minor
|
||||
}
|
||||
|
||||
const defaultMinorVersion = 16
|
||||
const defaultMinorVersion = 17
|
||||
|
||||
func genBuildTags() (string, error) {
|
||||
version := runtime.Version()
|
||||
|
||||
@@ -43,7 +43,7 @@ func TestPackages(t *testing.T) {
|
||||
// We check this one because it shows both defects when we break it: the value
|
||||
// gets corrupted, and the type becomes token.INT
|
||||
// TODO(mpl): if the ident between key and value becomes annoying, be smarter about it.
|
||||
contains: `"MaxFloat64": reflect.ValueOf(constant.MakeFromLiteral("179769313486231570814527423731704356798100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", token.FLOAT, 0)),`,
|
||||
contains: `"MaxFloat32": reflect.ValueOf(constant.MakeFromLiteral("340282346638528859811704183484516925440", token.FLOAT, 0)),`,
|
||||
},
|
||||
{
|
||||
desc: "using relative path, using go.mod",
|
||||
|
||||
@@ -3,8 +3,8 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"go/format"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
@@ -289,6 +289,13 @@ func {{$name}}Assign(n *node) {
|
||||
next := getExec(n.tnext)
|
||||
typ := n.typ.TypeOf()
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
setMap := isMapEntry(c0)
|
||||
var mapValue, indexValue func(*frame) reflect.Value
|
||||
|
||||
if setMap {
|
||||
mapValue = genValue(c0.child[0])
|
||||
indexValue = genValue(c0.child[1])
|
||||
}
|
||||
|
||||
if c1.rval.IsValid() {
|
||||
switch typ.Kind() {
|
||||
@@ -299,6 +306,9 @@ func {{$name}}Assign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, s := v0(f)
|
||||
v.SetString(s {{$op.Name}} v1)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
{{- end}}
|
||||
@@ -312,6 +322,9 @@ func {{$name}}Assign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetInt(i {{$op.Name}} j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -320,6 +333,9 @@ func {{$name}}Assign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetUint(i {{$op.Name}} j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
{{- if $op.Float}}
|
||||
@@ -329,6 +345,9 @@ func {{$name}}Assign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetFloat(i {{$op.Name}} j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
@@ -337,6 +356,9 @@ func {{$name}}Assign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v := v0(f)
|
||||
v.SetComplex(v.Complex() {{$op.Name}} v1)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
{{- end}}
|
||||
@@ -350,6 +372,9 @@ func {{$name}}Assign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, s := v0(f)
|
||||
v.SetString(s {{$op.Name}} v1(f).String())
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
{{- end}}
|
||||
@@ -364,6 +389,9 @@ func {{$name}}Assign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetInt(i {{$op.Name}} j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -373,6 +401,9 @@ func {{$name}}Assign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetUint(i {{$op.Name}} j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
{{- if $op.Float}}
|
||||
@@ -383,6 +414,9 @@ func {{$name}}Assign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetFloat(i {{$op.Name}} j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
@@ -391,6 +425,9 @@ func {{$name}}Assign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v := v0(f)
|
||||
v.SetComplex(v.Complex() {{$op.Name}} v1(f).Complex())
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
{{- end}}
|
||||
@@ -402,34 +439,54 @@ func {{$name}}Assign(n *node) {
|
||||
func {{$name}}(n *node) {
|
||||
next := getExec(n.tnext)
|
||||
typ := n.typ.TypeOf()
|
||||
c0 := n.child[0]
|
||||
setMap := isMapEntry(c0)
|
||||
var mapValue, indexValue func(*frame) reflect.Value
|
||||
|
||||
if setMap {
|
||||
mapValue = genValue(c0.child[0])
|
||||
indexValue = genValue(c0.child[1])
|
||||
}
|
||||
|
||||
switch typ.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
v0 := genValueInt(n.child[0])
|
||||
v0 := genValueInt(c0)
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetInt(i {{$op.Name}} 1)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
v0 := genValueUint(n.child[0])
|
||||
v0 := genValueUint(c0)
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetUint(i {{$op.Name}} 1)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
v0 := genValueFloat(n.child[0])
|
||||
v0 := genValueFloat(c0)
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetFloat(i {{$op.Name}} 1)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
v0 := genValue(n.child[0])
|
||||
v0 := genValue(c0)
|
||||
n.exec = func(f *frame) bltn {
|
||||
v := v0(f)
|
||||
v.SetComplex(v.Complex() {{$op.Name}} 1)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -500,7 +557,7 @@ func {{$name}}(n *node) {
|
||||
fnext := getExec(n.fnext)
|
||||
n.exec = func(f *frame) bltn {
|
||||
i1 := v1(f).Interface()
|
||||
if i0 != i1 {
|
||||
if i0 {{$op.Name}} i1 {
|
||||
dest(f).SetBool(true)
|
||||
return tnext
|
||||
}
|
||||
@@ -522,7 +579,7 @@ func {{$name}}(n *node) {
|
||||
fnext := getExec(n.fnext)
|
||||
n.exec = func(f *frame) bltn {
|
||||
i0 := v0(f).Interface()
|
||||
if i0 != i1 {
|
||||
if i0 {{$op.Name}} i1 {
|
||||
dest(f).SetBool(true)
|
||||
return tnext
|
||||
}
|
||||
@@ -545,7 +602,7 @@ func {{$name}}(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
i0 := v0(f).Interface()
|
||||
i1 := v1(f).Interface()
|
||||
if i0 != i1 {
|
||||
if i0 {{$op.Name}} i1 {
|
||||
dest(f).SetBool(true)
|
||||
return tnext
|
||||
}
|
||||
@@ -1126,7 +1183,7 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err = ioutil.WriteFile("op.go", source, 0666); err != nil {
|
||||
if err = os.WriteFile("op.go", source, 0o666); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
61
internal/unsafe2/unsafe.go
Normal file
61
internal/unsafe2/unsafe.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package unsafe2
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type dummy struct{}
|
||||
|
||||
// DummyType represents a stand-in for a recursive type.
|
||||
var DummyType = reflect.TypeOf(dummy{})
|
||||
|
||||
// the following type sizes must match their original definition in Go src/reflect/type.go.
|
||||
|
||||
type rtype struct {
|
||||
_ uintptr
|
||||
_ uintptr
|
||||
_ uint32
|
||||
_ uint32
|
||||
_ uintptr
|
||||
_ uintptr
|
||||
_ uint32
|
||||
_ uint32
|
||||
}
|
||||
|
||||
type emptyInterface struct {
|
||||
typ *rtype
|
||||
_ unsafe.Pointer
|
||||
}
|
||||
|
||||
type structField struct {
|
||||
_ uintptr
|
||||
typ *rtype
|
||||
_ uintptr
|
||||
}
|
||||
|
||||
type structType struct {
|
||||
rtype
|
||||
_ uintptr
|
||||
fields []structField
|
||||
}
|
||||
|
||||
// SetFieldType sets the type of the struct field at the given index, to the given type.
|
||||
//
|
||||
// The struct type must have been created at runtime. This is very unsafe.
|
||||
func SetFieldType(s reflect.Type, idx int, t reflect.Type) {
|
||||
if s.Kind() != reflect.Struct || idx >= s.NumField() {
|
||||
return
|
||||
}
|
||||
|
||||
rtyp := unpackType(s)
|
||||
styp := (*structType)(unsafe.Pointer(rtyp))
|
||||
f := styp.fields[idx]
|
||||
f.typ = unpackType(t)
|
||||
styp.fields[idx] = f
|
||||
}
|
||||
|
||||
func unpackType(t reflect.Type) *rtype {
|
||||
v := reflect.New(t).Elem().Interface()
|
||||
return (*emptyInterface)(unsafe.Pointer(&v)).typ
|
||||
}
|
||||
33
internal/unsafe2/unsafe_test.go
Normal file
33
internal/unsafe2/unsafe_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package unsafe2_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/traefik/yaegi/internal/unsafe2"
|
||||
)
|
||||
|
||||
func TestSwapFieldType(t *testing.T) {
|
||||
f := []reflect.StructField{
|
||||
{
|
||||
Name: "A",
|
||||
Type: reflect.TypeOf(int(0)),
|
||||
},
|
||||
{
|
||||
Name: "B",
|
||||
Type: reflect.PtrTo(unsafe2.DummyType),
|
||||
},
|
||||
{
|
||||
Name: "C",
|
||||
Type: reflect.TypeOf(int64(0)),
|
||||
},
|
||||
}
|
||||
typ := reflect.StructOf(f)
|
||||
ntyp := reflect.PtrTo(typ)
|
||||
|
||||
unsafe2.SetFieldType(typ, 1, ntyp)
|
||||
|
||||
if typ.Field(1).Type != ntyp {
|
||||
t.Fatalf("unexpected field type: want %s; got %s", ntyp, typ.Field(1).Type)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
@@ -93,7 +92,8 @@ const (
|
||||
switchIfStmt
|
||||
typeAssertExpr
|
||||
typeDecl
|
||||
typeSpec
|
||||
typeSpec // type A int
|
||||
typeSpecAssign // type A = int
|
||||
typeSwitch
|
||||
unaryExpr
|
||||
valueSpec
|
||||
@@ -176,6 +176,7 @@ var kinds = [...]string{
|
||||
typeAssertExpr: "typeAssertExpr",
|
||||
typeDecl: "typeDecl",
|
||||
typeSpec: "typeSpec",
|
||||
typeSpecAssign: "typeSpecAssign",
|
||||
typeSwitch: "typeSwitch",
|
||||
unaryExpr: "unaryExpr",
|
||||
valueSpec: "valueSpec",
|
||||
@@ -360,21 +361,14 @@ func wrapInMain(src string) string {
|
||||
return fmt.Sprintf("package main; func main() {%s\n}", src)
|
||||
}
|
||||
|
||||
// Note: no type analysis is performed at this stage, it is done in pre-order
|
||||
// processing of CFG, in order to accommodate forward type declarations.
|
||||
|
||||
// ast parses src string containing Go code and generates the corresponding AST.
|
||||
// The package name and the AST root node are returned.
|
||||
// The given name is used to set the filename of the relevant source file in the
|
||||
// interpreter's FileSet.
|
||||
func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error) {
|
||||
var inFunc bool
|
||||
func (interp *Interpreter) parse(src, name string, inc bool) (node ast.Node, err error) {
|
||||
mode := parser.DeclarationErrors
|
||||
|
||||
// Allow incremental parsing of declarations or statements, by inserting
|
||||
// them in a pseudo file package or function. Those statements or
|
||||
// declarations will be always evaluated in the global scope.
|
||||
var tok token.Token
|
||||
var inFunc bool
|
||||
if inc {
|
||||
tok = interp.firstToken(src)
|
||||
switch tok {
|
||||
@@ -391,18 +385,18 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
|
||||
}
|
||||
|
||||
if ok, err := interp.buildOk(&interp.context, name, src); !ok || err != nil {
|
||||
return "", nil, err // skip source not matching build constraints
|
||||
return nil, err // skip source not matching build constraints
|
||||
}
|
||||
|
||||
f, err := parser.ParseFile(interp.fset, name, src, mode)
|
||||
if err != nil {
|
||||
// only retry if we're on an expression/statement about a func
|
||||
if !inc || tok != token.FUNC {
|
||||
return "", nil, err
|
||||
return nil, err
|
||||
}
|
||||
// do not bother retrying if we know it's an error we're going to ignore later on.
|
||||
if ignoreError(err, src) {
|
||||
return "", nil, err
|
||||
return nil, err
|
||||
}
|
||||
// do not lose initial error, in case retrying fails.
|
||||
initialError := err
|
||||
@@ -410,16 +404,32 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
|
||||
src := wrapInMain(strings.TrimPrefix(src, "package main;"))
|
||||
f, err = parser.ParseFile(interp.fset, name, src, mode)
|
||||
if err != nil {
|
||||
return "", nil, initialError
|
||||
return nil, initialError
|
||||
}
|
||||
}
|
||||
|
||||
setYaegiTags(&interp.context, f.Comments)
|
||||
if inFunc {
|
||||
// return the body of the wrapper main function
|
||||
return f.Decls[0].(*ast.FuncDecl).Body, nil
|
||||
}
|
||||
|
||||
setYaegiTags(&interp.context, f.Comments)
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Note: no type analysis is performed at this stage, it is done in pre-order
|
||||
// processing of CFG, in order to accommodate forward type declarations.
|
||||
|
||||
// ast parses src string containing Go code and generates the corresponding AST.
|
||||
// The package name and the AST root node are returned.
|
||||
// The given name is used to set the filename of the relevant source file in the
|
||||
// interpreter's FileSet.
|
||||
func (interp *Interpreter) ast(f ast.Node) (string, *node, error) {
|
||||
var err error
|
||||
var root *node
|
||||
var anc astNode
|
||||
var st nodestack
|
||||
var pkgName string
|
||||
pkgName := "main"
|
||||
|
||||
addChild := func(root **node, anc astNode, pos token.Pos, kind nkind, act action) *node {
|
||||
var i interface{}
|
||||
@@ -627,7 +637,7 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
|
||||
}
|
||||
st.push(addChild(&root, anc, pos, kind, aNop), nod)
|
||||
|
||||
case *ast.CommentGroup:
|
||||
case *ast.CommentGroup, *ast.EmptyStmt:
|
||||
return false
|
||||
|
||||
case *ast.CompositeLit:
|
||||
@@ -680,6 +690,7 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
|
||||
|
||||
case *ast.FuncDecl:
|
||||
n := addChild(&root, anc, pos, funcDecl, aNop)
|
||||
n.val = n
|
||||
if a.Recv == nil {
|
||||
// function is not a method, create an empty receiver list
|
||||
addChild(&root, astNode{n, nod}, pos, fieldList, aNop)
|
||||
@@ -825,6 +836,10 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
|
||||
st.push(addChild(&root, anc, pos, typeAssertExpr, aTypeAssert), nod)
|
||||
|
||||
case *ast.TypeSpec:
|
||||
if a.Assign.IsValid() {
|
||||
st.push(addChild(&root, anc, pos, typeSpecAssign, aNop), nod)
|
||||
break
|
||||
}
|
||||
st.push(addChild(&root, anc, pos, typeSpec, aNop), nod)
|
||||
|
||||
case *ast.TypeSwitchStmt:
|
||||
@@ -891,15 +906,8 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
|
||||
}
|
||||
return true
|
||||
})
|
||||
if inFunc {
|
||||
// Incremental parsing: statements were inserted in a pseudo function.
|
||||
// Set root to function body so its statements are evaluated in global scope.
|
||||
root = root.child[1].child[3]
|
||||
root.anc = nil
|
||||
}
|
||||
if pkgName == "" {
|
||||
return "", root, errors.New("no package name found")
|
||||
}
|
||||
|
||||
interp.roots = append(interp.roots, root)
|
||||
return pkgName, root, err
|
||||
}
|
||||
|
||||
|
||||
@@ -178,6 +178,7 @@ var knownArch = map[string]bool{
|
||||
"amd64": true,
|
||||
"arm": true,
|
||||
"arm64": true,
|
||||
"loong64": true,
|
||||
"mips": true,
|
||||
"mips64": true,
|
||||
"mips64le": true,
|
||||
|
||||
235
interp/cfg.go
235
interp/cfg.go
@@ -49,9 +49,11 @@ const nilIdent = "nil"
|
||||
// and pre-compute frame sizes and indexes for all un-named (temporary) and named
|
||||
// variables. A list of nodes of init functions is returned.
|
||||
// Following this pass, the CFG is ready to run.
|
||||
func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
sc := interp.initScopePkg(importPath)
|
||||
check := typecheck{}
|
||||
func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string) ([]*node, error) {
|
||||
if sc == nil {
|
||||
sc = interp.initScopePkg(importPath, pkgName)
|
||||
}
|
||||
check := typecheck{scope: sc}
|
||||
var initNodes []*node
|
||||
var err error
|
||||
|
||||
@@ -62,6 +64,9 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if n.scope == nil {
|
||||
n.scope = sc
|
||||
}
|
||||
switch n.kind {
|
||||
case binaryExpr, unaryExpr, parenExpr:
|
||||
if isBoolAction(n) {
|
||||
@@ -145,10 +150,10 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
switch typ.Kind() {
|
||||
case reflect.Map:
|
||||
n.anc.gen = rangeMap
|
||||
ityp := &itype{cat: valueT, rtype: reflect.TypeOf((*reflect.MapIter)(nil))}
|
||||
ityp := valueTOf(reflect.TypeOf((*reflect.MapIter)(nil)))
|
||||
sc.add(ityp)
|
||||
ktyp = &itype{cat: valueT, rtype: typ.Key()}
|
||||
vtyp = &itype{cat: valueT, rtype: typ.Elem()}
|
||||
ktyp = valueTOf(typ.Key())
|
||||
vtyp = valueTOf(typ.Elem())
|
||||
case reflect.String:
|
||||
sc.add(sc.getType("int")) // Add a dummy type to store array shallow copy for range
|
||||
sc.add(sc.getType("int")) // Add a dummy type to store index for range
|
||||
@@ -157,11 +162,11 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
case reflect.Array, reflect.Slice:
|
||||
sc.add(sc.getType("int")) // Add a dummy type to store array shallow copy for range
|
||||
ktyp = sc.getType("int")
|
||||
vtyp = &itype{cat: valueT, rtype: typ.Elem()}
|
||||
vtyp = valueTOf(typ.Elem())
|
||||
}
|
||||
case mapT:
|
||||
n.anc.gen = rangeMap
|
||||
ityp := &itype{cat: valueT, rtype: reflect.TypeOf((*reflect.MapIter)(nil))}
|
||||
ityp := valueTOf(reflect.TypeOf((*reflect.MapIter)(nil)))
|
||||
sc.add(ityp)
|
||||
ktyp = o.typ.key
|
||||
vtyp = o.typ.val
|
||||
@@ -169,7 +174,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
ktyp = sc.getType("int")
|
||||
vtyp = o.typ.val
|
||||
if vtyp.cat == valueT {
|
||||
vtyp = &itype{cat: valueT, rtype: vtyp.rtype.Elem()}
|
||||
vtyp = valueTOf(vtyp.rtype.Elem())
|
||||
} else {
|
||||
vtyp = vtyp.val
|
||||
}
|
||||
@@ -284,19 +289,19 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
|
||||
case compositeLitExpr:
|
||||
if len(n.child) > 0 && n.child[0].isType(sc) {
|
||||
// Get type from 1st child
|
||||
// Get type from 1st child.
|
||||
if n.typ, err = nodeType(interp, sc, n.child[0]); err != nil {
|
||||
return false
|
||||
}
|
||||
// Indicate that the first child is the type
|
||||
// Indicate that the first child is the type.
|
||||
n.nleft = 1
|
||||
} else {
|
||||
// Get type from ancestor (implicit type)
|
||||
if n.anc.kind == keyValueExpr && n == n.anc.child[0] {
|
||||
n.typ = n.anc.typ.key
|
||||
} else if atyp := n.anc.typ; atyp != nil {
|
||||
if atyp.cat == valueT {
|
||||
n.typ = &itype{cat: valueT, rtype: atyp.rtype.Elem()}
|
||||
if atyp.cat == valueT && hasElem(atyp.rtype) {
|
||||
n.typ = valueTOf(atyp.rtype.Elem())
|
||||
} else {
|
||||
n.typ = atyp.val
|
||||
}
|
||||
@@ -410,11 +415,11 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
sc.loop = n
|
||||
|
||||
case importSpec:
|
||||
// already all done in gta
|
||||
// Already all done in GTA.
|
||||
return false
|
||||
|
||||
case typeSpec:
|
||||
// processing already done in GTA pass for global types, only parses inlined types
|
||||
// Processing already done in GTA pass for global types, only parses inlined types.
|
||||
if sc.def == nil {
|
||||
return false
|
||||
}
|
||||
@@ -424,13 +429,16 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
return false
|
||||
}
|
||||
if typ.incomplete {
|
||||
err = n.cfgErrorf("invalid type declaration")
|
||||
return false
|
||||
// Type may still be incomplete in case of a local recursive struct declaration.
|
||||
if typ, err = typ.finalize(); err != nil {
|
||||
err = n.cfgErrorf("invalid type declaration")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
switch n.child[1].kind {
|
||||
case identExpr, selectorExpr:
|
||||
n.typ = &itype{cat: aliasT, val: typ, name: typeName}
|
||||
n.typ = namedOf(typ, pkgName, typeName)
|
||||
default:
|
||||
n.typ = typ
|
||||
n.typ.name = typeName
|
||||
@@ -443,7 +451,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
// values which may be used in further declarations.
|
||||
if !sc.global {
|
||||
for _, c := range n.child {
|
||||
if _, err = interp.cfg(c, importPath); err != nil {
|
||||
if _, err = interp.cfg(c, sc, importPath, pkgName); err != nil {
|
||||
// No error processing here, to allow recovery in subtree nodes.
|
||||
err = nil
|
||||
}
|
||||
@@ -477,7 +485,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
break
|
||||
}
|
||||
|
||||
n.typ = &itype{cat: ptrT, val: n.child[0].typ}
|
||||
n.typ = ptrOf(n.child[0].typ)
|
||||
n.findex = sc.add(n.typ)
|
||||
|
||||
case assignStmt, defineStmt:
|
||||
@@ -513,7 +521,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
return
|
||||
}
|
||||
if src.typ.isBinMethod {
|
||||
dest.typ = &itype{cat: valueT, rtype: src.typ.methodCallType()}
|
||||
dest.typ = valueTOf(src.typ.methodCallType())
|
||||
} else {
|
||||
// In a new definition, propagate the source type to the destination
|
||||
// type. If the source is an untyped constant, make sure that the
|
||||
@@ -572,7 +580,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
dest.gen = nop
|
||||
case isFuncField(dest):
|
||||
// Setting a struct field of function type requires an extra step. Do not optimize.
|
||||
case isCall(src) && !isInterfaceSrc(dest.typ) && !isRecursiveField(dest) && n.kind != defineStmt:
|
||||
case isCall(src) && !isInterfaceSrc(dest.typ) && n.kind != defineStmt:
|
||||
// Call action may perform the assignment directly.
|
||||
if dest.typ.id() != src.typ.id() {
|
||||
// Skip optimitization if returned type doesn't match assigned one.
|
||||
@@ -594,8 +602,8 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
// Skip optimisation for assigned interface.
|
||||
break
|
||||
}
|
||||
if dest.action == aGetIndex {
|
||||
// Skip optimization, as it does not work when assigning to a struct field.
|
||||
if dest.action == aGetIndex || dest.action == aStar {
|
||||
// Skip optimization, as it does not work when assigning to a struct field or a dereferenced pointer.
|
||||
break
|
||||
}
|
||||
n.gen = nop
|
||||
@@ -657,7 +665,12 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
if r := lc.child[0].typ.numOut(); r != l {
|
||||
err = n.cfgErrorf("assignment mismatch: %d variables but %s returns %d values", l, lc.child[0].name(), r)
|
||||
}
|
||||
n.gen = nop
|
||||
if isBinCall(lc, sc) {
|
||||
n.gen = nop
|
||||
} else {
|
||||
// TODO (marc): skip if no conversion or wrapping is needed.
|
||||
n.gen = assignFromCall
|
||||
}
|
||||
case indexExpr:
|
||||
lc.gen = getIndexMap2
|
||||
n.gen = nop
|
||||
@@ -762,7 +775,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
case ptrT:
|
||||
n.typ = t.val
|
||||
if t.val.cat == valueT {
|
||||
n.typ = &itype{cat: valueT, rtype: t.val.rtype.Elem()}
|
||||
n.typ = valueTOf(t.val.rtype.Elem())
|
||||
} else {
|
||||
n.typ = t.val.val
|
||||
}
|
||||
@@ -772,7 +785,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
if t.rtype.Kind() == reflect.String {
|
||||
n.typ = sc.getType("byte")
|
||||
} else {
|
||||
n.typ = &itype{cat: valueT, rtype: t.rtype.Elem()}
|
||||
n.typ = valueTOf(t.rtype.Elem())
|
||||
}
|
||||
default:
|
||||
n.typ = t.val
|
||||
@@ -858,7 +871,9 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
|
||||
case labeledStmt:
|
||||
wireChild(n)
|
||||
n.start = n.child[1].start
|
||||
if len(n.child) > 1 {
|
||||
n.start = n.child[1].start
|
||||
}
|
||||
gotoLabel(n.sym)
|
||||
|
||||
case callExpr:
|
||||
@@ -885,7 +900,11 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
// Store result directly to frame output location, to avoid a frame copy.
|
||||
n.findex = 0
|
||||
case bname == "cap" && isInConstOrTypeDecl(n):
|
||||
switch n.child[1].typ.TypeOf().Kind() {
|
||||
t := n.child[1].typ.TypeOf()
|
||||
for t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
switch t.Kind() {
|
||||
case reflect.Array, reflect.Chan:
|
||||
capConst(n)
|
||||
default:
|
||||
@@ -894,7 +913,11 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
n.findex = notInFrame
|
||||
n.gen = nop
|
||||
case bname == "len" && isInConstOrTypeDecl(n):
|
||||
switch n.child[1].typ.TypeOf().Kind() {
|
||||
t := n.child[1].typ.TypeOf()
|
||||
for t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
switch t.Kind() {
|
||||
case reflect.Array, reflect.Chan, reflect.String:
|
||||
lenConst(n)
|
||||
default:
|
||||
@@ -955,7 +978,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
n.typ = c0.typ
|
||||
n.findex = sc.add(n.typ)
|
||||
}
|
||||
case isBinCall(n):
|
||||
case isBinCall(n, sc):
|
||||
err = check.arguments(n, n.child[1:], n.child[0], n.action == aCallSlice)
|
||||
if err != nil {
|
||||
break
|
||||
@@ -973,13 +996,13 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
sc.add(funcType.ret[i])
|
||||
}
|
||||
} else {
|
||||
n.typ = &itype{cat: valueT, rtype: typ.Out(0)}
|
||||
n.typ = valueTOf(typ.Out(0))
|
||||
if n.anc.kind == returnStmt {
|
||||
n.findex = childPos(n)
|
||||
} else {
|
||||
n.findex = sc.add(n.typ)
|
||||
for i := 1; i < typ.NumOut(); i++ {
|
||||
sc.add(&itype{cat: valueT, rtype: typ.Out(i)})
|
||||
sc.add(valueTOf(typ.Out(i)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -995,7 +1018,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
err = n.cfgErrorf("struct does not contain field: %s", c1.child[1].ident)
|
||||
break
|
||||
}
|
||||
n.typ = &itype{cat: valueT, rtype: reflect.TypeOf(field.Offset)}
|
||||
n.typ = valueTOf(reflect.TypeOf(field.Offset))
|
||||
n.rval = reflect.ValueOf(field.Offset)
|
||||
n.gen = nop
|
||||
default:
|
||||
@@ -1081,8 +1104,8 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
case reflect.Struct:
|
||||
err = check.structBinLitExpr(child, rtype)
|
||||
case reflect.Map:
|
||||
ktyp := &itype{cat: valueT, rtype: rtype.Key()}
|
||||
vtyp := &itype{cat: valueT, rtype: rtype.Elem()}
|
||||
ktyp := valueTOf(rtype.Key())
|
||||
vtyp := valueTOf(rtype.Elem())
|
||||
err = check.mapLitExpr(child, ktyp, vtyp)
|
||||
}
|
||||
}
|
||||
@@ -1223,7 +1246,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
|
||||
case funcDecl:
|
||||
n.start = n.child[3].start
|
||||
n.types = sc.types
|
||||
n.types, n.scope = sc.types, sc
|
||||
sc = sc.pop()
|
||||
funcName := n.child[1].ident
|
||||
if sym := sc.sym[funcName]; !isMethod(n) && sym != nil {
|
||||
@@ -1234,7 +1257,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
}
|
||||
|
||||
case funcLit:
|
||||
n.types = sc.types
|
||||
n.types, n.scope = sc.types, sc
|
||||
sc = sc.pop()
|
||||
err = genRun(n)
|
||||
|
||||
@@ -1255,7 +1278,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
// retry with the filename, in case ident is a package name.
|
||||
sym, level, found = sc.lookup(filepath.Join(n.ident, baseName))
|
||||
if !found {
|
||||
err = n.cfgErrorf("undefined: %s", n.ident)
|
||||
err = n.cfgErrorf("undefined: %s %d", n.ident, n.index)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -1480,9 +1503,9 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
pkg := n.child[0].sym.typ.path
|
||||
if s, ok := interp.binPkg[pkg][name]; ok {
|
||||
if isBinType(s) {
|
||||
n.typ = &itype{cat: valueT, rtype: s.Type().Elem()}
|
||||
n.typ = valueTOf(s.Type().Elem())
|
||||
} else {
|
||||
n.typ = &itype{cat: valueT, rtype: fixPossibleConstType(s.Type()), untyped: isValueUntyped(s)}
|
||||
n.typ = valueTOf(fixPossibleConstType(s.Type()), withUntyped(isValueUntyped(s)))
|
||||
n.rval = s
|
||||
}
|
||||
n.action = aGetSym
|
||||
@@ -1534,16 +1557,14 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
n.gen = getPtrIndexSeq
|
||||
if n.typ.cat == funcT {
|
||||
// Function in a struct field is always wrapped in reflect.Value.
|
||||
rtype := n.typ.TypeOf()
|
||||
n.typ = &itype{cat: valueT, rtype: rtype, val: n.typ}
|
||||
n.typ = wrapperValueTOf(n.typ.TypeOf(), n.typ)
|
||||
}
|
||||
default:
|
||||
n.gen = getIndexSeq
|
||||
n.typ = n.typ.fieldSeq(ti)
|
||||
if n.typ.cat == funcT {
|
||||
// Function in a struct field is always wrapped in reflect.Value.
|
||||
rtype := n.typ.TypeOf()
|
||||
n.typ = &itype{cat: valueT, rtype: rtype, val: n.typ}
|
||||
n.typ = wrapperValueTOf(n.typ.TypeOf(), n.typ)
|
||||
}
|
||||
}
|
||||
break
|
||||
@@ -1565,7 +1586,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
}
|
||||
}
|
||||
n.val = lind
|
||||
n.typ = &itype{cat: valueT, rtype: s.Type}
|
||||
n.typ = valueTOf(s.Type)
|
||||
break
|
||||
}
|
||||
// No field (embedded or not) matched. Try to match a method.
|
||||
@@ -1582,13 +1603,13 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
n.gen = getIndexBinMethod
|
||||
n.action = aGetMethod
|
||||
n.recv = &receiver{node: n.child[0]}
|
||||
n.typ = &itype{cat: valueT, rtype: method.Type, isBinMethod: true}
|
||||
n.typ = valueTOf(method.Type, isBinMethod())
|
||||
if hasRecvType {
|
||||
n.typ.recv = n.typ
|
||||
}
|
||||
case n.typ.rtype.Kind() == reflect.Ptr:
|
||||
if field, ok := n.typ.rtype.Elem().FieldByName(n.child[1].ident); ok {
|
||||
n.typ = &itype{cat: valueT, rtype: field.Type}
|
||||
n.typ = valueTOf(field.Type)
|
||||
n.val = field.Index
|
||||
n.gen = getPtrIndexSeq
|
||||
break
|
||||
@@ -1596,7 +1617,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
err = n.cfgErrorf("undefined field or method: %s", n.child[1].ident)
|
||||
case n.typ.rtype.Kind() == reflect.Struct:
|
||||
if field, ok := n.typ.rtype.FieldByName(n.child[1].ident); ok {
|
||||
n.typ = &itype{cat: valueT, rtype: field.Type}
|
||||
n.typ = valueTOf(field.Type)
|
||||
n.val = field.Index
|
||||
n.gen = getIndexSeq
|
||||
break
|
||||
@@ -1608,7 +1629,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
if m2, ok2 := pt.MethodByName(n.child[1].ident); ok2 {
|
||||
n.val = m2.Index
|
||||
n.gen = getIndexBinPtrMethod
|
||||
n.typ = &itype{cat: valueT, rtype: m2.Type, recv: &itype{cat: valueT, rtype: pt}, isBinMethod: true}
|
||||
n.typ = valueTOf(m2.Type, isBinMethod(), withRecv(valueTOf(pt)))
|
||||
n.recv = &receiver{node: n.child[0]}
|
||||
n.action = aGetMethod
|
||||
break
|
||||
@@ -1619,18 +1640,18 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
// Handle pointer on object defined in runtime
|
||||
if method, ok := n.typ.val.rtype.MethodByName(n.child[1].ident); ok {
|
||||
n.val = method.Index
|
||||
n.typ = &itype{cat: valueT, rtype: method.Type, recv: n.typ, isBinMethod: true}
|
||||
n.typ = valueTOf(method.Type, isBinMethod(), withRecv(n.typ))
|
||||
n.recv = &receiver{node: n.child[0]}
|
||||
n.gen = getIndexBinElemMethod
|
||||
n.action = aGetMethod
|
||||
} else if method, ok := reflect.PtrTo(n.typ.val.rtype).MethodByName(n.child[1].ident); ok {
|
||||
n.val = method.Index
|
||||
n.gen = getIndexBinMethod
|
||||
n.typ = &itype{cat: valueT, rtype: method.Type, recv: &itype{cat: valueT, rtype: reflect.PtrTo(n.typ.val.rtype)}, isBinMethod: true}
|
||||
n.typ = valueTOf(method.Type, withRecv(valueTOf(reflect.PtrTo(n.typ.val.rtype), isBinMethod())))
|
||||
n.recv = &receiver{node: n.child[0]}
|
||||
n.action = aGetMethod
|
||||
} else if field, ok := n.typ.val.rtype.FieldByName(n.child[1].ident); ok {
|
||||
n.typ = &itype{cat: valueT, rtype: field.Type}
|
||||
n.typ = valueTOf(field.Type)
|
||||
n.val = field.Index
|
||||
n.gen = getPtrIndexSeq
|
||||
} else {
|
||||
@@ -1655,14 +1676,17 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
}
|
||||
} else if m, lind, isPtr, ok := n.typ.lookupBinMethod(n.child[1].ident); ok {
|
||||
n.action = aGetMethod
|
||||
if isPtr && n.typ.fieldSeq(lind).cat != ptrT {
|
||||
switch {
|
||||
case isPtr && n.typ.fieldSeq(lind).cat != ptrT:
|
||||
n.gen = getIndexSeqPtrMethod
|
||||
} else {
|
||||
case isInterfaceSrc(n.typ):
|
||||
n.gen = getMethodByName
|
||||
default:
|
||||
n.gen = getIndexSeqMethod
|
||||
}
|
||||
n.recv = &receiver{node: n.child[0], index: lind}
|
||||
n.val = append([]int{m.Index}, lind...)
|
||||
n.typ = &itype{cat: valueT, rtype: m.Type, recv: n.child[0].typ, isBinMethod: true}
|
||||
n.typ = valueTOf(m.Type, isBinMethod(), withRecv(n.child[0].typ))
|
||||
} else {
|
||||
err = n.cfgErrorf("undefined selector: %s", n.child[1].ident)
|
||||
}
|
||||
@@ -1731,7 +1755,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
case n.child[0].isType(sc):
|
||||
// pointer type expression
|
||||
n.gen = nop
|
||||
n.typ = &itype{cat: ptrT, val: n.child[0].typ}
|
||||
n.typ = ptrOf(n.child[0].typ)
|
||||
default:
|
||||
// dereference expression
|
||||
wireChild(n)
|
||||
@@ -1742,7 +1766,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
}
|
||||
|
||||
if c0 := n.child[0]; c0.typ.cat == valueT {
|
||||
n.typ = &itype{cat: valueT, rtype: c0.typ.rtype.Elem()}
|
||||
n.typ = valueTOf(c0.typ.rtype.Elem())
|
||||
} else {
|
||||
n.typ = c0.typ.val
|
||||
}
|
||||
@@ -1876,7 +1900,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
if n.anc.action != aAssignX {
|
||||
if c0.typ.cat == valueT && isFunc(c1.typ) {
|
||||
// Avoid special wrapping of interfaces and func types.
|
||||
n.typ = &itype{cat: valueT, rtype: c1.typ.TypeOf()}
|
||||
n.typ = valueTOf(c1.typ.TypeOf())
|
||||
} else {
|
||||
n.typ = c1.typ
|
||||
}
|
||||
@@ -1908,7 +1932,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
|
||||
if n.action == aRecv {
|
||||
// Channel receive operation: set type to the channel data type
|
||||
if n.typ.cat == valueT {
|
||||
n.typ = &itype{cat: valueT, rtype: n.typ.rtype.Elem()}
|
||||
n.typ = valueTOf(n.typ.rtype.Elem())
|
||||
} else {
|
||||
n.typ = n.typ.val
|
||||
}
|
||||
@@ -1992,18 +2016,23 @@ func compDefineX(sc *scope, n *node) error {
|
||||
if funtype.cat == valueT {
|
||||
// Handle functions imported from runtime.
|
||||
for i := 0; i < funtype.rtype.NumOut(); i++ {
|
||||
types = append(types, &itype{cat: valueT, rtype: funtype.rtype.Out(i)})
|
||||
types = append(types, valueTOf(funtype.rtype.Out(i)))
|
||||
}
|
||||
} else {
|
||||
types = funtype.ret
|
||||
}
|
||||
if n.child[l-1].isType(sc) {
|
||||
if n.anc.kind == varDecl && n.child[l-1].isType(sc) {
|
||||
l--
|
||||
}
|
||||
if len(types) != l {
|
||||
return n.cfgErrorf("assignment mismatch: %d variables but %s returns %d values", l, src.child[0].name(), len(types))
|
||||
}
|
||||
n.gen = nop
|
||||
if isBinCall(src, sc) {
|
||||
n.gen = nop
|
||||
} else {
|
||||
// TODO (marc): skip if no conversion or wrapping is needed.
|
||||
n.gen = assignFromCall
|
||||
}
|
||||
|
||||
case indexExpr:
|
||||
types = append(types, src.typ, sc.getType("bool"))
|
||||
@@ -2402,25 +2431,11 @@ func isField(n *node) bool {
|
||||
return n.kind == selectorExpr && len(n.child) > 0 && n.child[0].typ != nil && isStruct(n.child[0].typ)
|
||||
}
|
||||
|
||||
func isRecursiveField(n *node) bool {
|
||||
if !isField(n) {
|
||||
return false
|
||||
}
|
||||
t := n.typ
|
||||
for t != nil {
|
||||
if t.recursive {
|
||||
return true
|
||||
}
|
||||
t = t.val
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isInConstOrTypeDecl(n *node) bool {
|
||||
anc := n.anc
|
||||
for anc != nil {
|
||||
switch anc.kind {
|
||||
case constDecl, typeDecl:
|
||||
case constDecl, typeDecl, arrayType, chanType:
|
||||
return true
|
||||
case varDecl, funcDecl:
|
||||
return false
|
||||
@@ -2466,8 +2481,19 @@ func isCall(n *node) bool {
|
||||
return n.action == aCall || n.action == aCallSlice
|
||||
}
|
||||
|
||||
func isBinCall(n *node) bool {
|
||||
return isCall(n) && n.child[0].typ.cat == valueT && n.child[0].typ.rtype.Kind() == reflect.Func
|
||||
func isBinCall(n *node, sc *scope) bool {
|
||||
if !isCall(n) || len(n.child) == 0 {
|
||||
return false
|
||||
}
|
||||
c0 := n.child[0]
|
||||
if c0.typ == nil {
|
||||
// If called early in parsing, child type may not be known yet.
|
||||
c0.typ, _ = nodeType(n.interp, sc, c0)
|
||||
if c0.typ == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return c0.typ.cat == valueT && c0.typ.rtype.Kind() == reflect.Func
|
||||
}
|
||||
|
||||
func isOffsetof(n *node) bool {
|
||||
@@ -2573,7 +2599,7 @@ func gotoLabel(s *symbol) {
|
||||
func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerator) {
|
||||
switch typ.cat {
|
||||
case aliasT, ptrT:
|
||||
gen = compositeGenerator(n, n.typ.val, rtyp)
|
||||
gen = compositeGenerator(n, typ.val, rtyp)
|
||||
case arrayT, sliceT:
|
||||
gen = arrayLit
|
||||
case mapT:
|
||||
@@ -2627,23 +2653,54 @@ func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerat
|
||||
// arrayTypeLen returns the node's array length. If the expression is an
|
||||
// array variable it is determined from the value's type, otherwise it is
|
||||
// computed from the source definition.
|
||||
func arrayTypeLen(n *node) int {
|
||||
func arrayTypeLen(n *node, sc *scope) (int, error) {
|
||||
if n.typ != nil && n.typ.cat == arrayT {
|
||||
return n.typ.length
|
||||
return n.typ.length, nil
|
||||
}
|
||||
max := -1
|
||||
for i, c := range n.child[1:] {
|
||||
r := i
|
||||
if c.kind == keyValueExpr {
|
||||
if v := c.child[0].rval; v.IsValid() {
|
||||
r = int(c.child[0].rval.Int())
|
||||
for _, c := range n.child[1:] {
|
||||
var r int
|
||||
|
||||
if c.kind != keyValueExpr {
|
||||
r = max + 1
|
||||
max = r
|
||||
continue
|
||||
}
|
||||
|
||||
c0 := c.child[0]
|
||||
v := c0.rval
|
||||
if v.IsValid() {
|
||||
r = int(v.Int())
|
||||
} else {
|
||||
// Resolve array key value as a constant.
|
||||
if c0.kind == identExpr {
|
||||
// Key is defined by a symbol which must be a constant integer.
|
||||
sym, _, ok := sc.lookup(c0.ident)
|
||||
if !ok {
|
||||
return 0, c0.cfgErrorf("undefined: %s", c0.ident)
|
||||
}
|
||||
if sym.kind != constSym {
|
||||
return 0, c0.cfgErrorf("non-constant array bound %q", c0.ident)
|
||||
}
|
||||
r = int(vInt(sym.rval))
|
||||
} else {
|
||||
// Key is defined by a numeric constant expression.
|
||||
if _, err := c0.interp.cfg(c0, sc, sc.pkgID, sc.pkgName); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
cv, ok := c0.rval.Interface().(constant.Value)
|
||||
if !ok {
|
||||
return 0, c0.cfgErrorf("non-constant expression")
|
||||
}
|
||||
r = constToInt(cv)
|
||||
}
|
||||
}
|
||||
|
||||
if r > max {
|
||||
max = r
|
||||
}
|
||||
}
|
||||
return max + 1
|
||||
return max + 1, nil
|
||||
}
|
||||
|
||||
// isValueUntyped returns true if value is untyped.
|
||||
|
||||
84
interp/compile_test.go
Normal file
84
interp/compile_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"testing"
|
||||
|
||||
"github.com/traefik/yaegi/stdlib"
|
||||
)
|
||||
|
||||
func TestCompileAST(t *testing.T) {
|
||||
file, err := parser.ParseFile(token.NewFileSet(), "_.go", `
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Foo struct{}
|
||||
|
||||
var foo Foo
|
||||
const bar = "asdf"
|
||||
|
||||
func main() {
|
||||
fmt.Println(1)
|
||||
}
|
||||
`, 0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(file.Imports) != 1 || len(file.Decls) != 5 {
|
||||
panic("wrong number of imports or decls")
|
||||
}
|
||||
|
||||
dType := file.Decls[1].(*ast.GenDecl)
|
||||
dVar := file.Decls[2].(*ast.GenDecl)
|
||||
dConst := file.Decls[3].(*ast.GenDecl)
|
||||
dFunc := file.Decls[4].(*ast.FuncDecl)
|
||||
|
||||
if dType.Tok != token.TYPE {
|
||||
panic("decl[1] is not a type")
|
||||
}
|
||||
if dVar.Tok != token.VAR {
|
||||
panic("decl[2] is not a var")
|
||||
}
|
||||
if dConst.Tok != token.CONST {
|
||||
panic("decl[3] is not a const")
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
node ast.Node
|
||||
skip string
|
||||
}{
|
||||
{desc: "file", node: file},
|
||||
{desc: "import", node: file.Imports[0]},
|
||||
{desc: "type", node: dType},
|
||||
{desc: "var", node: dVar, skip: "not supported"},
|
||||
{desc: "const", node: dConst},
|
||||
{desc: "func", node: dFunc},
|
||||
{desc: "block", node: dFunc.Body},
|
||||
{desc: "expr", node: dFunc.Body.List[0]},
|
||||
}
|
||||
|
||||
i := New(Options{})
|
||||
_ = i.Use(stdlib.Symbols)
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
if c.skip != "" {
|
||||
t.Skip(c.skip)
|
||||
}
|
||||
|
||||
i := i
|
||||
if _, ok := c.node.(*ast.File); ok {
|
||||
i = New(Options{})
|
||||
_ = i.Use(stdlib.Symbols)
|
||||
}
|
||||
_, err := i.CompileAST(c.node)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to compile %s: %v", c.desc, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
730
interp/debugger.go
Normal file
730
interp/debugger.go
Normal file
@@ -0,0 +1,730 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotLive indicates that the specified ID does not refer to a (live) Go
|
||||
// routine.
|
||||
ErrNotLive = errors.New("not live")
|
||||
|
||||
// ErrRunning indicates that the specified Go routine is running.
|
||||
ErrRunning = errors.New("running")
|
||||
|
||||
// ErrNotRunning indicates that the specified Go routine is running.
|
||||
ErrNotRunning = errors.New("not running")
|
||||
)
|
||||
|
||||
var rNodeType = reflect.TypeOf((*node)(nil)).Elem()
|
||||
|
||||
// A Debugger can be used to debug a Yaegi program.
|
||||
type Debugger struct {
|
||||
interp *Interpreter
|
||||
events func(*DebugEvent)
|
||||
context context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
gWait *sync.WaitGroup
|
||||
gLock *sync.Mutex
|
||||
gID int
|
||||
gLive map[int]*debugRoutine
|
||||
|
||||
result reflect.Value
|
||||
err error
|
||||
}
|
||||
|
||||
// go routine debug state.
|
||||
type debugRoutine struct {
|
||||
id int
|
||||
|
||||
mode DebugEventReason
|
||||
running bool
|
||||
resume chan struct{}
|
||||
|
||||
fDepth int
|
||||
fStep int
|
||||
}
|
||||
|
||||
// node debug state.
|
||||
type nodeDebugData struct {
|
||||
program *Program
|
||||
breakOnLine bool
|
||||
breakOnCall bool
|
||||
}
|
||||
|
||||
// frame debug state.
|
||||
type frameDebugData struct {
|
||||
g *debugRoutine
|
||||
node *node
|
||||
name string
|
||||
kind frameKind
|
||||
scope *scope
|
||||
}
|
||||
|
||||
// frame kind.
|
||||
type frameKind int
|
||||
|
||||
const (
|
||||
// interpreter root frame.
|
||||
frameRoot frameKind = iota + 1
|
||||
|
||||
// function call frame.
|
||||
frameCall
|
||||
|
||||
// closure capture frame.
|
||||
frameClosure
|
||||
)
|
||||
|
||||
// DebugOptions are the debugger options.
|
||||
type DebugOptions struct {
|
||||
// If true, Go routine IDs start at 1 instead of 0.
|
||||
GoRoutineStartAt1 bool
|
||||
}
|
||||
|
||||
// A DebugEvent is an event generated by a debugger.
|
||||
type DebugEvent struct {
|
||||
debugger *Debugger
|
||||
reason DebugEventReason
|
||||
frame *frame
|
||||
}
|
||||
|
||||
// DebugFrame provides access to stack frame information while debugging a
|
||||
// program.
|
||||
type DebugFrame struct {
|
||||
event *DebugEvent
|
||||
frames []*frame
|
||||
}
|
||||
|
||||
// DebugFrameScope provides access to scoped variables while debugging a
|
||||
// program.
|
||||
type DebugFrameScope struct {
|
||||
frame *frame
|
||||
}
|
||||
|
||||
// DebugVariable is the name and value of a variable from a debug session.
|
||||
type DebugVariable struct {
|
||||
Name string
|
||||
Value reflect.Value
|
||||
}
|
||||
|
||||
// DebugGoRoutine provides access to information about a Go routine while
|
||||
// debugging a program.
|
||||
type DebugGoRoutine struct {
|
||||
id int
|
||||
}
|
||||
|
||||
// Breakpoint is the result of attempting to set a breakpoint.
|
||||
type Breakpoint struct {
|
||||
// Valid indicates whether the breakpoint was successfully set.
|
||||
Valid bool
|
||||
|
||||
// Position indicates the source position of the breakpoint.
|
||||
Position token.Position
|
||||
}
|
||||
|
||||
// DebugEventReason is the reason a debug event occurred.
|
||||
type DebugEventReason int
|
||||
|
||||
const (
|
||||
// continue execution normally.
|
||||
debugRun DebugEventReason = iota
|
||||
|
||||
// DebugPause is emitted when a pause request is completed. Can be used with
|
||||
// Interrupt to request a pause.
|
||||
DebugPause
|
||||
|
||||
// DebugBreak is emitted when a debug target hits a breakpoint.
|
||||
DebugBreak
|
||||
|
||||
// DebugEntry is emitted when a debug target starts executing. Can be used
|
||||
// with Step to produce a corresponding event when execution starts.
|
||||
DebugEntry
|
||||
|
||||
// DebugStepInto is emitted when a stepInto request is completed. Can be
|
||||
// used with Step or Interrupt to request a stepInto.
|
||||
DebugStepInto
|
||||
|
||||
// DebugStepOver is emitted when a stepOver request is completed. Can be
|
||||
// used with Step or Interrupt to request a stepOver.
|
||||
DebugStepOver
|
||||
|
||||
// DebugStepOut is emitted when a stepOut request is completed. Can be used
|
||||
// with Step or Interrupt to request a stepOut.
|
||||
DebugStepOut
|
||||
|
||||
// DebugTerminate is emitted when a debug target terminates. Can be used
|
||||
// with Interrupt to attempt to terminate the program.
|
||||
DebugTerminate
|
||||
|
||||
// DebugEnterGoRoutine is emitted when a Go routine is entered.
|
||||
DebugEnterGoRoutine
|
||||
|
||||
// DebugExitGoRoutine is emitted when a Go routine is exited.
|
||||
DebugExitGoRoutine
|
||||
)
|
||||
|
||||
// Debug initializes a debugger for the given program.
|
||||
//
|
||||
// The program will not start running until Step or Continue has been called. If
|
||||
// Step is called with DebugEntry, an entry event will be generated before the
|
||||
// first statement is executed. Otherwise, the debugger will behave as usual.
|
||||
func (interp *Interpreter) Debug(ctx context.Context, prog *Program, events func(*DebugEvent), opts *DebugOptions) *Debugger {
|
||||
dbg := new(Debugger)
|
||||
dbg.interp = interp
|
||||
dbg.events = events
|
||||
dbg.context, dbg.cancel = context.WithCancel(ctx)
|
||||
dbg.gWait = new(sync.WaitGroup)
|
||||
dbg.gLock = new(sync.Mutex)
|
||||
dbg.gLive = make(map[int]*debugRoutine, 1)
|
||||
|
||||
if opts == nil {
|
||||
opts = new(DebugOptions)
|
||||
}
|
||||
if opts.GoRoutineStartAt1 {
|
||||
dbg.gID = 1
|
||||
}
|
||||
|
||||
mainG := dbg.enterGoRoutine()
|
||||
mainG.mode = DebugEntry
|
||||
|
||||
interp.debugger = dbg
|
||||
interp.frame.debug = &frameDebugData{kind: frameRoot, g: mainG}
|
||||
|
||||
prog.root.Walk(func(n *node) bool {
|
||||
n.setProgram(prog)
|
||||
return true
|
||||
}, nil)
|
||||
|
||||
go func() {
|
||||
defer func() { interp.debugger = nil }()
|
||||
defer events(&DebugEvent{reason: DebugTerminate})
|
||||
defer dbg.cancel()
|
||||
|
||||
<-mainG.resume
|
||||
dbg.events(&DebugEvent{dbg, DebugEnterGoRoutine, interp.frame})
|
||||
dbg.result, dbg.err = interp.ExecuteWithContext(ctx, prog)
|
||||
dbg.exitGoRoutine(mainG)
|
||||
dbg.events(&DebugEvent{dbg, DebugExitGoRoutine, interp.frame})
|
||||
dbg.gWait.Wait()
|
||||
}()
|
||||
|
||||
return dbg
|
||||
}
|
||||
|
||||
// Wait blocks until all Go routines launched by the program have terminated.
|
||||
// Wait returns the results of `(*Interpreter).Execute`.
|
||||
func (dbg *Debugger) Wait() (reflect.Value, error) {
|
||||
<-dbg.context.Done()
|
||||
return dbg.result, dbg.err
|
||||
}
|
||||
|
||||
// mark entry into a go routine.
|
||||
func (dbg *Debugger) enterGoRoutine() *debugRoutine {
|
||||
g := new(debugRoutine)
|
||||
g.resume = make(chan struct{})
|
||||
|
||||
dbg.gWait.Add(1)
|
||||
|
||||
dbg.gLock.Lock()
|
||||
g.id = dbg.gID
|
||||
dbg.gID++
|
||||
dbg.gLive[g.id] = g
|
||||
dbg.gLock.Unlock()
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
// mark exit from a go routine.
|
||||
func (dbg *Debugger) exitGoRoutine(g *debugRoutine) {
|
||||
dbg.gLock.Lock()
|
||||
delete(dbg.gLive, g.id)
|
||||
dbg.gLock.Unlock()
|
||||
|
||||
dbg.gWait.Done()
|
||||
}
|
||||
|
||||
// get the state for a given go routine, if it's live.
|
||||
func (dbg *Debugger) getGoRoutine(id int) (*debugRoutine, bool) {
|
||||
dbg.gLock.Lock()
|
||||
g, ok := dbg.gLive[id]
|
||||
dbg.gLock.Unlock()
|
||||
return g, ok
|
||||
}
|
||||
|
||||
// mark entry into a function call.
|
||||
func (dbg *Debugger) enterCall(nFunc, nCall *node, f *frame) {
|
||||
if f.debug != nil {
|
||||
f.debug.g.fDepth++
|
||||
return
|
||||
}
|
||||
|
||||
f.debug = new(frameDebugData)
|
||||
f.debug.g = f.anc.debug.g
|
||||
f.debug.scope = nFunc.scope
|
||||
|
||||
switch nFunc.kind {
|
||||
case funcLit:
|
||||
f.debug.kind = frameCall
|
||||
if nFunc.frame != nil {
|
||||
nFunc.frame.debug.kind = frameClosure
|
||||
nFunc.frame.debug.node = nFunc
|
||||
}
|
||||
|
||||
case funcDecl:
|
||||
f.debug.kind = frameCall
|
||||
f.debug.name = nFunc.child[1].ident
|
||||
}
|
||||
|
||||
if nCall != nil && nCall.anc.kind == goStmt {
|
||||
f.debug.g = dbg.enterGoRoutine()
|
||||
dbg.events(&DebugEvent{dbg, DebugEnterGoRoutine, f})
|
||||
}
|
||||
|
||||
f.debug.g.fDepth++
|
||||
}
|
||||
|
||||
// mark exit from a function call.
|
||||
func (dbg *Debugger) exitCall(nFunc, nCall *node, f *frame) {
|
||||
_ = nFunc // ignore unused, so exitCall can have the same signature as enterCall
|
||||
|
||||
f.debug.g.fDepth--
|
||||
|
||||
if nCall != nil && nCall.anc.kind == goStmt {
|
||||
dbg.exitGoRoutine(f.debug.g)
|
||||
dbg.events(&DebugEvent{dbg, DebugExitGoRoutine, f})
|
||||
}
|
||||
}
|
||||
|
||||
// called by the interpreter prior to executing the node.
|
||||
func (dbg *Debugger) exec(n *node, f *frame) (stop bool) {
|
||||
f.debug.node = n
|
||||
|
||||
if n != nil && n.pos == token.NoPos {
|
||||
return false
|
||||
}
|
||||
|
||||
g := f.debug.g
|
||||
defer func() { g.running = true }()
|
||||
|
||||
e := &DebugEvent{dbg, g.mode, f}
|
||||
switch {
|
||||
case g.mode == DebugTerminate:
|
||||
dbg.cancel()
|
||||
return true
|
||||
|
||||
case n.shouldBreak():
|
||||
e.reason = DebugBreak
|
||||
|
||||
case g.mode == debugRun:
|
||||
return false
|
||||
|
||||
case g.mode == DebugStepOut:
|
||||
if g.fDepth >= g.fStep {
|
||||
return false
|
||||
}
|
||||
|
||||
case g.mode == DebugStepOver:
|
||||
if g.fDepth > g.fStep {
|
||||
return false
|
||||
}
|
||||
}
|
||||
dbg.events(e)
|
||||
|
||||
g.running = false
|
||||
select {
|
||||
case <-g.resume:
|
||||
return false
|
||||
case <-dbg.context.Done():
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Continue continues execution of the specified Go routine. Continue returns
|
||||
// ErrNotLive if there is no Go routine with the corresponding ID, or if it is not
|
||||
// live.
|
||||
func (dbg *Debugger) Continue(id int) error {
|
||||
g, ok := dbg.getGoRoutine(id)
|
||||
if !ok {
|
||||
return ErrNotLive
|
||||
}
|
||||
|
||||
g.mode = debugRun
|
||||
g.resume <- struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// update the exec mode of this routine.
|
||||
func (g *debugRoutine) setMode(reason DebugEventReason) {
|
||||
if g.mode == DebugTerminate {
|
||||
return
|
||||
}
|
||||
|
||||
if g.mode == DebugEntry && reason == DebugEntry {
|
||||
return
|
||||
}
|
||||
|
||||
switch reason {
|
||||
case DebugStepInto, DebugStepOver, DebugStepOut:
|
||||
g.mode, g.fStep = reason, g.fDepth
|
||||
default:
|
||||
g.mode = DebugPause
|
||||
}
|
||||
}
|
||||
|
||||
// Step issues a stepInto, stepOver, or stepOut request to a stopped Go routine.
|
||||
// Step returns ErrRunning if the Go routine is running. Step returns ErrNotLive
|
||||
// if there is no Go routine with the corresponding ID, or if it is not live.
|
||||
func (dbg *Debugger) Step(id int, reason DebugEventReason) error {
|
||||
g, ok := dbg.getGoRoutine(id)
|
||||
if !ok {
|
||||
return ErrNotLive
|
||||
}
|
||||
|
||||
if g.running {
|
||||
return ErrRunning
|
||||
}
|
||||
|
||||
g.setMode(reason)
|
||||
g.resume <- struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Interrupt issues a stepInto, stepOver, or stepOut request to a running Go
|
||||
// routine. Interrupt returns ErrRunning if the Go routine is running. Interrupt
|
||||
// returns ErrNotLive if there is no Go routine with the corresponding ID, or if
|
||||
// it is not live.
|
||||
func (dbg *Debugger) Interrupt(id int, reason DebugEventReason) bool {
|
||||
g, ok := dbg.getGoRoutine(id)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
g.setMode(reason)
|
||||
return true
|
||||
}
|
||||
|
||||
// Terminate attempts to terminate the program.
|
||||
func (dbg *Debugger) Terminate() {
|
||||
dbg.gLock.Lock()
|
||||
g := dbg.gLive
|
||||
dbg.gLive = nil
|
||||
dbg.gLock.Unlock()
|
||||
|
||||
for _, g := range g {
|
||||
g.mode = DebugTerminate
|
||||
close(g.resume)
|
||||
}
|
||||
}
|
||||
|
||||
// BreakpointTarget is the target of a request to set breakpoints.
|
||||
type BreakpointTarget func(*Debugger, func(*node))
|
||||
|
||||
// PathBreakpointTarget is used to set breapoints on compiled code by path. This
|
||||
// can be used to set breakpoints on code compiled with EvalPath, or source
|
||||
// packages loaded by Yaegi.
|
||||
func PathBreakpointTarget(path string) BreakpointTarget {
|
||||
return func(dbg *Debugger, cb func(*node)) {
|
||||
for _, r := range dbg.interp.roots {
|
||||
f := dbg.interp.fset.File(r.pos)
|
||||
if f != nil && f.Name() == path {
|
||||
cb(r)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ProgramBreakpointTarget is used to set breakpoints on a Program.
|
||||
func ProgramBreakpointTarget(prog *Program) BreakpointTarget {
|
||||
return func(_ *Debugger, cb func(*node)) {
|
||||
cb(prog.root)
|
||||
}
|
||||
}
|
||||
|
||||
// AllBreakpointTarget is used to set breakpoints on all compiled code. Do not
|
||||
// use with LineBreakpoint.
|
||||
func AllBreakpointTarget() BreakpointTarget {
|
||||
return func(dbg *Debugger, cb func(*node)) {
|
||||
for _, r := range dbg.interp.roots {
|
||||
cb(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type breakpointSetup struct {
|
||||
roots []*node
|
||||
lines map[int]int
|
||||
funcs map[string]int
|
||||
}
|
||||
|
||||
// BreakpointRequest is a request to set a breakpoint.
|
||||
type BreakpointRequest func(*breakpointSetup, int)
|
||||
|
||||
// LineBreakpoint requests a breakpoint on the given line.
|
||||
func LineBreakpoint(line int) BreakpointRequest {
|
||||
return func(b *breakpointSetup, i int) {
|
||||
b.lines[line] = i
|
||||
}
|
||||
}
|
||||
|
||||
// FunctionBreakpoint requests a breakpoint on the named function.
|
||||
func FunctionBreakpoint(name string) BreakpointRequest {
|
||||
return func(b *breakpointSetup, i int) {
|
||||
b.funcs[name] = i
|
||||
}
|
||||
}
|
||||
|
||||
// SetBreakpoints sets breakpoints for the given target. The returned array has
|
||||
// an entry for every request, in order. If a given breakpoint request cannot be
|
||||
// satisfied, the corresponding entry will be marked invalid. If the target
|
||||
// cannot be found, all entries will be marked invalid.
|
||||
func (dbg *Debugger) SetBreakpoints(target BreakpointTarget, requests ...BreakpointRequest) []Breakpoint {
|
||||
// start with all breakpoints unverified
|
||||
results := make([]Breakpoint, len(requests))
|
||||
|
||||
// prepare all the requests
|
||||
setup := new(breakpointSetup)
|
||||
target(dbg, func(root *node) {
|
||||
setup.roots = append(setup.roots, root)
|
||||
setup.lines = make(map[int]int, len(requests))
|
||||
setup.funcs = make(map[string]int, len(requests))
|
||||
for i, rq := range requests {
|
||||
rq(setup, i)
|
||||
}
|
||||
})
|
||||
|
||||
// find breakpoints
|
||||
for _, root := range setup.roots {
|
||||
root.Walk(func(n *node) bool {
|
||||
// function breakpoints
|
||||
if len(setup.funcs) > 0 && n.kind == funcDecl {
|
||||
// reset stale breakpoints
|
||||
n.start.setBreakOnCall(false)
|
||||
|
||||
if i, ok := setup.funcs[n.child[1].ident]; ok && !results[i].Valid {
|
||||
results[i].Valid = true
|
||||
results[i].Position = dbg.interp.fset.Position(n.start.pos)
|
||||
n.start.setBreakOnCall(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// line breakpoints
|
||||
if len(setup.lines) > 0 && n.pos.IsValid() && n.action != aNop && getExec(n) != nil {
|
||||
// reset stale breakpoints
|
||||
n.setBreakOnLine(false)
|
||||
|
||||
pos := dbg.interp.fset.Position(n.pos)
|
||||
if i, ok := setup.lines[pos.Line]; ok && !results[i].Valid {
|
||||
results[i].Valid = true
|
||||
results[i].Position = pos
|
||||
n.setBreakOnLine(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}, nil)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// GoRoutines returns an array of live Go routines.
|
||||
func (dbg *Debugger) GoRoutines() []*DebugGoRoutine {
|
||||
dbg.gLock.Lock()
|
||||
r := make([]*DebugGoRoutine, 0, len(dbg.gLive))
|
||||
for id := range dbg.gLive {
|
||||
r = append(r, &DebugGoRoutine{id})
|
||||
}
|
||||
dbg.gLock.Unlock()
|
||||
sort.Slice(r, func(i, j int) bool { return r[i].id < r[j].id })
|
||||
return r
|
||||
}
|
||||
|
||||
// ID returns the ID of the Go routine.
|
||||
func (r *DebugGoRoutine) ID() int { return r.id }
|
||||
|
||||
// Name returns "Goroutine {ID}".
|
||||
func (r *DebugGoRoutine) Name() string { return fmt.Sprintf("Goroutine %d", r.id) }
|
||||
|
||||
// GoRoutine returns the ID of the Go routine that generated the event.
|
||||
func (evt *DebugEvent) GoRoutine() int {
|
||||
if evt.frame.debug == nil {
|
||||
return 0
|
||||
}
|
||||
return evt.frame.debug.g.id
|
||||
}
|
||||
|
||||
// Reason returns the reason for the event.
|
||||
func (evt *DebugEvent) Reason() DebugEventReason {
|
||||
return evt.reason
|
||||
}
|
||||
|
||||
// Walk the stack trace frames. The root frame is included if and only if it is
|
||||
// the only frame. Closure frames are rolled up into the following call frame.
|
||||
func (evt *DebugEvent) walkFrames(fn func([]*frame) bool) {
|
||||
if evt.frame == evt.frame.root {
|
||||
fn([]*frame{evt.frame})
|
||||
return
|
||||
}
|
||||
|
||||
var g *debugRoutine
|
||||
if evt.frame.debug != nil {
|
||||
g = evt.frame.debug.g
|
||||
}
|
||||
|
||||
var frames []*frame
|
||||
for f := evt.frame; f != nil && f != f.root && (f.debug == nil || f.debug.g == g); f = f.anc {
|
||||
if f.debug == nil || f.debug.kind != frameCall {
|
||||
frames = append(frames, f)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(frames) > 0 {
|
||||
if !fn(frames) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
frames = frames[:0]
|
||||
frames = append(frames, f)
|
||||
}
|
||||
|
||||
if len(frames) > 0 {
|
||||
fn(frames)
|
||||
}
|
||||
}
|
||||
|
||||
// FrameDepth returns the number of call frames in the stack trace.
|
||||
func (evt *DebugEvent) FrameDepth() int {
|
||||
if evt.frame == evt.frame.root {
|
||||
return 1
|
||||
}
|
||||
|
||||
var n int
|
||||
evt.walkFrames(func([]*frame) bool { n++; return true })
|
||||
return n
|
||||
}
|
||||
|
||||
// Frames returns the call frames in the range [start, end).
|
||||
func (evt *DebugEvent) Frames(start, end int) []*DebugFrame {
|
||||
count := end - start
|
||||
if count < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
frames := []*DebugFrame{}
|
||||
evt.walkFrames(func(f []*frame) bool {
|
||||
df := &DebugFrame{evt, make([]*frame, len(f))}
|
||||
copy(df.frames, f)
|
||||
frames = append(frames, df)
|
||||
return len(frames) < count
|
||||
})
|
||||
return frames
|
||||
}
|
||||
|
||||
// Name returns the name of the stack frame. For function calls to named
|
||||
// functions, this is the function name.
|
||||
func (f *DebugFrame) Name() string {
|
||||
d := f.frames[0].debug
|
||||
if d == nil {
|
||||
return "<unknown>"
|
||||
}
|
||||
switch d.kind {
|
||||
case frameRoot:
|
||||
return "<init>"
|
||||
case frameClosure:
|
||||
return "<closure>"
|
||||
case frameCall:
|
||||
if d.name == "" {
|
||||
return "<anonymous>"
|
||||
}
|
||||
return d.name
|
||||
default:
|
||||
return "<unknown>"
|
||||
}
|
||||
}
|
||||
|
||||
// Position returns the current position of the frame. This is effectively the
|
||||
// program counter/link register. May return `Position{}`.
|
||||
func (f *DebugFrame) Position() token.Position {
|
||||
d := f.frames[0].debug
|
||||
if d == nil || d.node == nil {
|
||||
return token.Position{}
|
||||
}
|
||||
return f.event.debugger.interp.fset.Position(d.node.pos)
|
||||
}
|
||||
|
||||
// Program returns the program associated with the current position of the
|
||||
// frame. May return nil.
|
||||
func (f *DebugFrame) Program() *Program {
|
||||
d := f.frames[0].debug
|
||||
if d == nil || d.node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return d.node.debug.program
|
||||
}
|
||||
|
||||
// Scopes returns the variable scopes of the frame.
|
||||
func (f *DebugFrame) Scopes() []*DebugFrameScope {
|
||||
s := make([]*DebugFrameScope, len(f.frames))
|
||||
for i, f := range f.frames {
|
||||
s[i] = &DebugFrameScope{f}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// IsClosure returns true if this is the capture scope of a closure.
|
||||
func (f *DebugFrameScope) IsClosure() bool {
|
||||
return f.frame.debug != nil && f.frame.debug.kind == frameClosure
|
||||
}
|
||||
|
||||
// Variables returns the names and values of the variables of the scope.
|
||||
func (f *DebugFrameScope) Variables() []*DebugVariable {
|
||||
d := f.frame.debug
|
||||
if d == nil || d.scope == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
index := map[int]string{}
|
||||
scanScope(d.scope, index)
|
||||
|
||||
m := make([]*DebugVariable, 0, len(f.frame.data))
|
||||
for i, v := range f.frame.data {
|
||||
if typ := v.Type(); typ.AssignableTo(rNodeType) || typ.Kind() == reflect.Ptr && typ.Elem().AssignableTo(rNodeType) {
|
||||
continue
|
||||
}
|
||||
name, ok := index[i]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
m = append(m, &DebugVariable{name, v})
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func scanScope(sc *scope, index map[int]string) {
|
||||
for name, sym := range sc.sym {
|
||||
if _, ok := index[sym.index]; ok {
|
||||
continue
|
||||
}
|
||||
index[sym.index] = name
|
||||
}
|
||||
|
||||
for _, ch := range sc.child {
|
||||
if ch.def != sc.def {
|
||||
continue
|
||||
}
|
||||
scanScope(ch, index)
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package interp
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -69,7 +68,7 @@ func (nopCloser) Close() error { return nil }
|
||||
// dotWriter returns an output stream to a dot(1) co-process where to write data in .dot format.
|
||||
func dotWriter(dotCmd string) io.WriteCloser {
|
||||
if dotCmd == "" {
|
||||
return nopCloser{ioutil.Discard}
|
||||
return nopCloser{io.Discard}
|
||||
}
|
||||
fields := strings.Fields(dotCmd)
|
||||
cmd := exec.Command(fields[0], fields[1:]...)
|
||||
|
||||
120
interp/gta.go
120
interp/gta.go
@@ -1,8 +1,8 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// gta performs a global types analysis on the AST, registering types,
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
// All function bodies are skipped. GTA is necessary to handle out of
|
||||
// order declarations and multiple source files packages.
|
||||
// rpath is the relative path to the directory containing the source for the package.
|
||||
func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, error) {
|
||||
sc := interp.initScopePkg(importPath)
|
||||
func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([]*node, error) {
|
||||
sc := interp.initScopePkg(importPath, pkgName)
|
||||
var err error
|
||||
var revisit []*node
|
||||
|
||||
@@ -25,7 +25,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
|
||||
case constDecl:
|
||||
// Early parse of constDecl subtree, to compute all constant
|
||||
// values which may be used in further declarations.
|
||||
if _, err = interp.cfg(n, importPath); err != nil {
|
||||
if _, err = interp.cfg(n, sc, importPath, pkgName); err != nil {
|
||||
// No error processing here, to allow recovery in subtree nodes.
|
||||
// TODO(marc): check for a non recoverable error and return it for better diagnostic.
|
||||
err = nil
|
||||
@@ -37,10 +37,17 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
|
||||
}
|
||||
|
||||
case defineStmt:
|
||||
var atyp *itype
|
||||
var (
|
||||
atyp *itype
|
||||
err2 error
|
||||
)
|
||||
if n.nleft+n.nright < len(n.child) {
|
||||
// Type is declared explicitly in the assign expression.
|
||||
if atyp, err = nodeType(interp, sc, n.child[n.nleft]); err != nil {
|
||||
if atyp, err2 = nodeType(interp, sc, n.child[n.nleft]); err2 != nil {
|
||||
// The type does not exist yet, stash the error and come back
|
||||
// when the type is known.
|
||||
n.meta = err2
|
||||
revisit = append(revisit, n)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -52,9 +59,9 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
|
||||
|
||||
for i := 0; i < n.nleft; i++ {
|
||||
dest, src := n.child[i], n.child[sbase+i]
|
||||
val := reflect.ValueOf(sc.iota)
|
||||
val := src.rval
|
||||
if n.anc.kind == constDecl {
|
||||
if _, err2 := interp.cfg(n, importPath); err2 != nil {
|
||||
if _, err2 := interp.cfg(n, sc, importPath, pkgName); err2 != nil {
|
||||
// Constant value can not be computed yet.
|
||||
// Come back when child dependencies are known.
|
||||
revisit = append(revisit, n)
|
||||
@@ -63,7 +70,11 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
|
||||
}
|
||||
typ := atyp
|
||||
if typ == nil {
|
||||
if typ, err = nodeType(interp, sc, src); err != nil {
|
||||
if typ, err2 = nodeType(interp, sc, src); err2 != nil || typ == nil {
|
||||
// The type does is not known yet, stash the error and come back
|
||||
// when the type is known.
|
||||
n.meta = err2
|
||||
revisit = append(revisit, n)
|
||||
return false
|
||||
}
|
||||
val = src.rval
|
||||
@@ -78,7 +89,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
|
||||
return false
|
||||
}
|
||||
if typ.isBinMethod {
|
||||
typ = &itype{cat: valueT, rtype: typ.methodCallType(), isBinMethod: true, scope: sc}
|
||||
typ = valueTOf(typ.methodCallType(), isBinMethod(), withScope(sc))
|
||||
}
|
||||
sc.sym[dest.ident] = &symbol{kind: varSym, global: true, index: sc.add(typ), typ: typ, rval: val, node: n}
|
||||
if n.anc.kind == constDecl {
|
||||
@@ -138,27 +149,28 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
|
||||
n.ident = ident
|
||||
rcvr := n.child[0].child[0]
|
||||
rtn := rcvr.lastChild()
|
||||
typeName := rtn.ident
|
||||
if typeName == "" {
|
||||
// The receiver is a pointer, retrieve typeName from indirection
|
||||
typeName = rtn.child[0].ident
|
||||
elementType := sc.getType(typeName)
|
||||
if elementType == nil {
|
||||
// Add type if necessary, so method can be registered
|
||||
sc.sym[typeName] = &symbol{kind: typeSym, typ: &itype{name: typeName, path: importPath, incomplete: true, node: rtn.child[0], scope: sc}}
|
||||
elementType = sc.sym[typeName].typ
|
||||
}
|
||||
rcvrtype = &itype{cat: ptrT, val: elementType, incomplete: elementType.incomplete, node: rtn, scope: sc}
|
||||
elementType.method = append(elementType.method, n)
|
||||
} else {
|
||||
rcvrtype = sc.getType(typeName)
|
||||
if rcvrtype == nil {
|
||||
// Add type if necessary, so method can be registered
|
||||
sc.sym[typeName] = &symbol{kind: typeSym, typ: &itype{name: typeName, path: importPath, incomplete: true, node: rtn, scope: sc}}
|
||||
rcvrtype = sc.sym[typeName].typ
|
||||
}
|
||||
typName, typPtr := rtn.ident, false
|
||||
if typName == "" {
|
||||
typName, typPtr = rtn.child[0].ident, true
|
||||
}
|
||||
rcvrtype.method = append(rcvrtype.method, n)
|
||||
sym, _, found := sc.lookup(typName)
|
||||
if !found {
|
||||
n.meta = n.cfgErrorf("undefined: %s", typName)
|
||||
revisit = append(revisit, n)
|
||||
return false
|
||||
}
|
||||
if sym.kind != typeSym || (sym.node != nil && sym.node.kind == typeSpecAssign) {
|
||||
err = n.cfgErrorf("cannot define new methods on non-local type %s", baseType(sym.typ).id())
|
||||
return false
|
||||
}
|
||||
rcvrtype = sym.typ
|
||||
if typPtr {
|
||||
elementType := sym.typ
|
||||
rcvrtype = ptrOf(elementType, withNode(rtn), withScope(sc))
|
||||
rcvrtype.incomplete = elementType.incomplete
|
||||
elementType.addMethod(n)
|
||||
}
|
||||
rcvrtype.addMethod(n)
|
||||
n.child[0].child[0].lastChild().typ = rcvrtype
|
||||
case ident == "init":
|
||||
// init functions do not get declared as per the Go spec.
|
||||
@@ -187,6 +199,9 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
|
||||
}
|
||||
// Try to import a binary package first, or a source package
|
||||
var pkgName string
|
||||
if packageName := path.Base(ipath); path.Dir(ipath) == packageName {
|
||||
ipath = packageName
|
||||
}
|
||||
if pkg := interp.binPkg[ipath]; pkg != nil {
|
||||
switch name {
|
||||
case "_": // no import of symbols
|
||||
@@ -196,7 +211,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
|
||||
if isBinType(v) {
|
||||
typ = typ.Elem()
|
||||
}
|
||||
sc.sym[n] = &symbol{kind: binSym, typ: &itype{cat: valueT, rtype: typ, scope: sc}, rval: v}
|
||||
sc.sym[n] = &symbol{kind: binSym, typ: valueTOf(typ, withScope(sc)), rval: v}
|
||||
}
|
||||
default: // import symbols in package namespace
|
||||
if name == "" {
|
||||
@@ -250,7 +265,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
|
||||
err = n.cfgErrorf("import %q error: %v", ipath, err)
|
||||
}
|
||||
|
||||
case typeSpec:
|
||||
case typeSpec, typeSpecAssign:
|
||||
typeName := n.child[0].ident
|
||||
var typ *itype
|
||||
if typ, err = nodeType(interp, sc, n.child[1]); err != nil {
|
||||
@@ -261,13 +276,16 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
|
||||
|
||||
switch n.child[1].kind {
|
||||
case identExpr, selectorExpr:
|
||||
n.typ = &itype{cat: aliasT, val: typ, name: typeName, path: importPath, field: typ.field, incomplete: typ.incomplete, scope: sc, node: n.child[0]}
|
||||
n.typ = namedOf(typ, pkgName, typeName, withNode(n.child[0]), withScope(sc))
|
||||
n.typ.incomplete = typ.incomplete
|
||||
n.typ.field = typ.field
|
||||
copy(n.typ.method, typ.method)
|
||||
default:
|
||||
n.typ = typ
|
||||
n.typ.name = typeName
|
||||
n.typ.path = importPath
|
||||
n.typ.path = pkgName
|
||||
}
|
||||
n.typ.str = n.typ.path + "." + n.typ.name
|
||||
|
||||
asImportName := filepath.Join(typeName, baseName)
|
||||
if _, exists := sc.sym[asImportName]; exists {
|
||||
@@ -277,15 +295,21 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
|
||||
}
|
||||
sym, exists := sc.sym[typeName]
|
||||
if !exists {
|
||||
sc.sym[typeName] = &symbol{kind: typeSym}
|
||||
sc.sym[typeName] = &symbol{kind: typeSym, node: n}
|
||||
} else {
|
||||
if sym.typ != nil && (len(sym.typ.method) > 0) {
|
||||
if n.kind == typeSpecAssign {
|
||||
err = n.cfgErrorf("cannot define new methods on non-local type %s", baseType(typ).id())
|
||||
return false
|
||||
}
|
||||
// Type has already been seen as a receiver in a method function
|
||||
n.typ.method = append(n.typ.method, sym.typ.method...)
|
||||
for _, m := range sym.typ.method {
|
||||
n.typ.addMethod(m)
|
||||
}
|
||||
} else {
|
||||
// TODO(mpl): figure out how to detect redeclarations without breaking type aliases.
|
||||
// Allow redeclarations for now.
|
||||
sc.sym[typeName] = &symbol{kind: typeSym}
|
||||
sc.sym[typeName] = &symbol{kind: typeSym, node: n}
|
||||
}
|
||||
}
|
||||
sc.sym[typeName].typ = n.typ
|
||||
@@ -303,12 +327,23 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
|
||||
return revisit, err
|
||||
}
|
||||
|
||||
func baseType(t *itype) *itype {
|
||||
for {
|
||||
switch t.cat {
|
||||
case ptrT, aliasT:
|
||||
t = t.val
|
||||
default:
|
||||
return t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// gtaRetry (re)applies gta until all global constants and types are defined.
|
||||
func (interp *Interpreter) gtaRetry(nodes []*node, importPath string) error {
|
||||
func (interp *Interpreter) gtaRetry(nodes []*node, importPath, pkgName string) error {
|
||||
revisit := []*node{}
|
||||
for {
|
||||
for _, n := range nodes {
|
||||
list, err := interp.gta(n, importPath, importPath)
|
||||
list, err := interp.gta(n, importPath, importPath, pkgName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -325,10 +360,15 @@ func (interp *Interpreter) gtaRetry(nodes []*node, importPath string) error {
|
||||
|
||||
if len(revisit) > 0 {
|
||||
n := revisit[0]
|
||||
if n.kind == typeSpec {
|
||||
switch n.kind {
|
||||
case typeSpec, typeSpecAssign:
|
||||
if err := definedType(n.typ); err != nil {
|
||||
return err
|
||||
}
|
||||
case defineStmt, funcDecl:
|
||||
if err, ok := n.meta.(error); ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return n.cfgErrorf("constant definition loop")
|
||||
}
|
||||
|
||||
333
interp/interp.go
333
interp/interp.go
@@ -7,18 +7,18 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"go/constant"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"io/fs"
|
||||
"log"
|
||||
"math/bits"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
|
||||
// Interpreter node structure for AST and CFG.
|
||||
type node struct {
|
||||
debug *nodeDebugData // debug info
|
||||
child []*node // child subtrees (AST)
|
||||
anc *node // ancestor (AST)
|
||||
start *node // entry point in subtree (CFG)
|
||||
@@ -45,12 +46,53 @@ type node struct {
|
||||
typ *itype // type of value in frame, or nil
|
||||
recv *receiver // method receiver node for call, or nil
|
||||
types []reflect.Type // frame types, used by function literals only
|
||||
scope *scope // frame scope
|
||||
action action // action
|
||||
exec bltn // generated function to execute
|
||||
gen bltnGenerator // generator function to produce above bltn
|
||||
val interface{} // static generic value (CFG execution)
|
||||
rval reflect.Value // reflection value to let runtime access interpreter (CFG)
|
||||
ident string // set if node is a var or func
|
||||
meta interface{} // meta stores meta information between gta runs, like errors
|
||||
}
|
||||
|
||||
func (n *node) shouldBreak() bool {
|
||||
if n == nil || n.debug == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if n.debug.breakOnLine || n.debug.breakOnCall {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (n *node) setProgram(p *Program) {
|
||||
if n.debug == nil {
|
||||
n.debug = new(nodeDebugData)
|
||||
}
|
||||
n.debug.program = p
|
||||
}
|
||||
|
||||
func (n *node) setBreakOnCall(v bool) {
|
||||
if n.debug == nil {
|
||||
if !v {
|
||||
return
|
||||
}
|
||||
n.debug = new(nodeDebugData)
|
||||
}
|
||||
n.debug.breakOnCall = v
|
||||
}
|
||||
|
||||
func (n *node) setBreakOnLine(v bool) {
|
||||
if n.debug == nil {
|
||||
if !v {
|
||||
return
|
||||
}
|
||||
n.debug = new(nodeDebugData)
|
||||
}
|
||||
n.debug.breakOnLine = v
|
||||
}
|
||||
|
||||
// receiver stores method receiver object access path.
|
||||
@@ -67,6 +109,8 @@ type frame struct {
|
||||
// Located at start of struct to ensure proper aligment.
|
||||
id uint64
|
||||
|
||||
debug *frameDebugData
|
||||
|
||||
root *frame // global space
|
||||
anc *frame // ancestor frame (caller space)
|
||||
data []reflect.Value // values
|
||||
@@ -104,6 +148,7 @@ func (f *frame) clone(fork bool) *frame {
|
||||
recovered: f.recovered,
|
||||
id: f.runid(),
|
||||
done: f.done,
|
||||
debug: f.debug,
|
||||
}
|
||||
if fork {
|
||||
nf.data = make([]reflect.Value, len(f.data))
|
||||
@@ -124,18 +169,22 @@ type imports map[string]map[string]*symbol
|
||||
|
||||
// opt stores interpreter options.
|
||||
type opt struct {
|
||||
astDot bool // display AST graph (debug)
|
||||
cfgDot bool // display CFG graph (debug)
|
||||
// dotCmd is the command to process the dot graph produced when astDot and/or
|
||||
// cfgDot is enabled. It defaults to 'dot -Tdot -o <filename>.dot'.
|
||||
dotCmd string
|
||||
noRun bool // compile, but do not run
|
||||
fastChan bool // disable cancellable chan operations
|
||||
context build.Context // build context: GOPATH, build constraints
|
||||
specialStdio bool // Allows os.Stdin, os.Stdout, os.Stderr to not be file descriptors
|
||||
stdin io.Reader // standard input
|
||||
stdout io.Writer // standard output
|
||||
stderr io.Writer // standard error
|
||||
context build.Context // build context: GOPATH, build constraints
|
||||
stdin io.Reader // standard input
|
||||
stdout io.Writer // standard output
|
||||
stderr io.Writer // standard error
|
||||
args []string // cmdline args
|
||||
env map[string]string // environment of interpreter, entries in form of "key=value"
|
||||
filesystem fs.FS // filesystem containing sources
|
||||
astDot bool // display AST graph (debug)
|
||||
cfgDot bool // display CFG graph (debug)
|
||||
noRun bool // compile, but do not run
|
||||
fastChan bool // disable cancellable chan operations
|
||||
specialStdio bool // allows os.Stdin, os.Stdout, os.Stderr to not be file descriptors
|
||||
unrestricted bool // allow use of non sandboxed symbols
|
||||
}
|
||||
|
||||
// Interpreter contains global resources and state.
|
||||
@@ -166,8 +215,11 @@ type Interpreter struct {
|
||||
srcPkg imports // source packages used in interpreter, indexed by path
|
||||
pkgNames map[string]string // package names, indexed by import path
|
||||
done chan struct{} // for cancellation of channel operations
|
||||
roots []*node
|
||||
|
||||
hooks *hooks // symbol hooks
|
||||
|
||||
debugger *Debugger
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -253,12 +305,27 @@ type Options struct {
|
||||
// They default to os.Stdin, os.Stdout and os.Stderr respectively.
|
||||
Stdin io.Reader
|
||||
Stdout, Stderr io.Writer
|
||||
|
||||
// Cmdline args, defaults to os.Args.
|
||||
Args []string
|
||||
|
||||
// Environment of interpreter. Entries are in the form "key=values".
|
||||
Env []string
|
||||
|
||||
// SourcecodeFilesystem is where the _sourcecode_ is loaded from and does
|
||||
// NOT affect the filesystem of scripts when they run.
|
||||
// It can be any fs.FS compliant filesystem (e.g. embed.FS, or fstest.MapFS for testing)
|
||||
// See example/fs/fs_test.go for an example.
|
||||
SourcecodeFilesystem fs.FS
|
||||
|
||||
// Unrestricted allows to run non sandboxed stdlib symbols such as os/exec and environment
|
||||
Unrestricted bool
|
||||
}
|
||||
|
||||
// New returns a new interpreter.
|
||||
func New(options Options) *Interpreter {
|
||||
i := Interpreter{
|
||||
opt: opt{context: build.Default},
|
||||
opt: opt{context: build.Default, filesystem: &realFS{}, env: map[string]string{}},
|
||||
frame: newFrame(nil, 0, 0),
|
||||
fset: token.NewFileSet(),
|
||||
universe: initUniverse(),
|
||||
@@ -282,6 +349,28 @@ func New(options Options) *Interpreter {
|
||||
i.opt.stderr = os.Stderr
|
||||
}
|
||||
|
||||
if i.opt.args = options.Args; i.opt.args == nil {
|
||||
i.opt.args = os.Args
|
||||
}
|
||||
|
||||
// unrestricted allows to use non sandboxed stdlib symbols and env.
|
||||
if options.Unrestricted {
|
||||
i.opt.unrestricted = true
|
||||
} else {
|
||||
for _, e := range options.Env {
|
||||
a := strings.SplitN(e, "=", 2)
|
||||
if len(a) == 2 {
|
||||
i.opt.env[a[0]] = a[1]
|
||||
} else {
|
||||
i.opt.env[a[0]] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if options.SourcecodeFilesystem != nil {
|
||||
i.opt.filesystem = options.SourcecodeFilesystem
|
||||
}
|
||||
|
||||
i.opt.context.GOPATH = options.GoPath
|
||||
if len(options.BuildTags) > 0 {
|
||||
i.opt.context.BuildTags = options.BuildTags
|
||||
@@ -304,7 +393,8 @@ func New(options Options) *Interpreter {
|
||||
// fastChan disables the cancellable version of channel operations in evalWithContext
|
||||
i.opt.fastChan, _ = strconv.ParseBool(os.Getenv("YAEGI_FAST_CHAN"))
|
||||
|
||||
// specialStdio allows to assign directly io.Writer and io.Reader to os.Stdxxx, even if they are not file descriptors.
|
||||
// specialStdio allows to assign directly io.Writer and io.Reader to os.Stdxxx,
|
||||
// even if they are not file descriptors.
|
||||
i.opt.specialStdio, _ = strconv.ParseBool(os.Getenv("YAEGI_SPECIAL_STDIO"))
|
||||
|
||||
return &i
|
||||
@@ -331,27 +421,27 @@ const (
|
||||
func initUniverse() *scope {
|
||||
sc := &scope{global: true, sym: map[string]*symbol{
|
||||
// predefined Go types
|
||||
"bool": {kind: typeSym, typ: &itype{cat: boolT, name: "bool"}},
|
||||
"byte": {kind: typeSym, typ: &itype{cat: uint8T, name: "uint8"}},
|
||||
"complex64": {kind: typeSym, typ: &itype{cat: complex64T, name: "complex64"}},
|
||||
"complex128": {kind: typeSym, typ: &itype{cat: complex128T, name: "complex128"}},
|
||||
"error": {kind: typeSym, typ: &itype{cat: errorT, name: "error"}},
|
||||
"float32": {kind: typeSym, typ: &itype{cat: float32T, name: "float32"}},
|
||||
"float64": {kind: typeSym, typ: &itype{cat: float64T, name: "float64"}},
|
||||
"int": {kind: typeSym, typ: &itype{cat: intT, name: "int"}},
|
||||
"int8": {kind: typeSym, typ: &itype{cat: int8T, name: "int8"}},
|
||||
"int16": {kind: typeSym, typ: &itype{cat: int16T, name: "int16"}},
|
||||
"int32": {kind: typeSym, typ: &itype{cat: int32T, name: "int32"}},
|
||||
"int64": {kind: typeSym, typ: &itype{cat: int64T, name: "int64"}},
|
||||
"interface{}": {kind: typeSym, typ: &itype{cat: interfaceT}},
|
||||
"rune": {kind: typeSym, typ: &itype{cat: int32T, name: "int32"}},
|
||||
"string": {kind: typeSym, typ: &itype{cat: stringT, name: "string"}},
|
||||
"uint": {kind: typeSym, typ: &itype{cat: uintT, name: "uint"}},
|
||||
"uint8": {kind: typeSym, typ: &itype{cat: uint8T, name: "uint8"}},
|
||||
"uint16": {kind: typeSym, typ: &itype{cat: uint16T, name: "uint16"}},
|
||||
"uint32": {kind: typeSym, typ: &itype{cat: uint32T, name: "uint32"}},
|
||||
"uint64": {kind: typeSym, typ: &itype{cat: uint64T, name: "uint64"}},
|
||||
"uintptr": {kind: typeSym, typ: &itype{cat: uintptrT, name: "uintptr"}},
|
||||
"bool": {kind: typeSym, typ: &itype{cat: boolT, name: "bool", str: "bool"}},
|
||||
"byte": {kind: typeSym, typ: &itype{cat: uint8T, name: "uint8", str: "uint8"}},
|
||||
"complex64": {kind: typeSym, typ: &itype{cat: complex64T, name: "complex64", str: "complex64"}},
|
||||
"complex128": {kind: typeSym, typ: &itype{cat: complex128T, name: "complex128", str: "complex128"}},
|
||||
"error": {kind: typeSym, typ: &itype{cat: errorT, name: "error", str: "error"}},
|
||||
"float32": {kind: typeSym, typ: &itype{cat: float32T, name: "float32", str: "float32"}},
|
||||
"float64": {kind: typeSym, typ: &itype{cat: float64T, name: "float64", str: "float64"}},
|
||||
"int": {kind: typeSym, typ: &itype{cat: intT, name: "int", str: "int"}},
|
||||
"int8": {kind: typeSym, typ: &itype{cat: int8T, name: "int8", str: "int8"}},
|
||||
"int16": {kind: typeSym, typ: &itype{cat: int16T, name: "int16", str: "int16"}},
|
||||
"int32": {kind: typeSym, typ: &itype{cat: int32T, name: "int32", str: "int32"}},
|
||||
"int64": {kind: typeSym, typ: &itype{cat: int64T, name: "int64", str: "int64"}},
|
||||
"interface{}": {kind: typeSym, typ: &itype{cat: interfaceT, str: "interface{}"}},
|
||||
"rune": {kind: typeSym, typ: &itype{cat: int32T, name: "int32", str: "int32"}},
|
||||
"string": {kind: typeSym, typ: &itype{cat: stringT, name: "string", str: "string"}},
|
||||
"uint": {kind: typeSym, typ: &itype{cat: uintT, name: "uint", str: "uint"}},
|
||||
"uint8": {kind: typeSym, typ: &itype{cat: uint8T, name: "uint8", str: "uint8"}},
|
||||
"uint16": {kind: typeSym, typ: &itype{cat: uint16T, name: "uint16", str: "uint16"}},
|
||||
"uint32": {kind: typeSym, typ: &itype{cat: uint32T, name: "uint32", str: "uint32"}},
|
||||
"uint64": {kind: typeSym, typ: &itype{cat: uint64T, name: "uint64", str: "uint64"}},
|
||||
"uintptr": {kind: typeSym, typ: &itype{cat: uintptrT, name: "uintptr", str: "uintptr"}},
|
||||
|
||||
// predefined Go constants
|
||||
"false": {kind: constSym, typ: untypedBool(), rval: reflect.ValueOf(false)},
|
||||
@@ -359,7 +449,7 @@ func initUniverse() *scope {
|
||||
"iota": {kind: constSym, typ: untypedInt()},
|
||||
|
||||
// predefined Go zero value
|
||||
"nil": {typ: &itype{cat: nilT, untyped: true}},
|
||||
"nil": {typ: &itype{cat: nilT, untyped: true, str: "nil"}},
|
||||
|
||||
// predefined Go builtins
|
||||
bltnAppend: {kind: bltnSym, builtin: _append},
|
||||
@@ -406,18 +496,42 @@ func (interp *Interpreter) Eval(src string) (res reflect.Value, err error) {
|
||||
// by the interpreter, and a non nil error in case of failure.
|
||||
// The main function of the main package is executed if present.
|
||||
func (interp *Interpreter) EvalPath(path string) (res reflect.Value, err error) {
|
||||
if !isFile(path) {
|
||||
if !isFile(interp.opt.filesystem, path) {
|
||||
_, err := interp.importSrc(mainID, path, NoTest)
|
||||
return res, err
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(path)
|
||||
b, err := fs.ReadFile(interp.filesystem, path)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
return interp.eval(string(b), path, false)
|
||||
}
|
||||
|
||||
// EvalPathWithContext evaluates Go code located at path and returns the last
|
||||
// result computed by the interpreter, and a non nil error in case of failure.
|
||||
// The main function of the main package is executed if present.
|
||||
func (interp *Interpreter) EvalPathWithContext(ctx context.Context, path string) (res reflect.Value, err error) {
|
||||
interp.mutex.Lock()
|
||||
interp.done = make(chan struct{})
|
||||
interp.cancelChan = !interp.opt.fastChan
|
||||
interp.mutex.Unlock()
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
res, err = interp.EvalPath(path)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
interp.stop()
|
||||
return reflect.Value{}, ctx.Err()
|
||||
case <-done:
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
// EvalTest evaluates Go code located at path, including test files with "_test.go" suffix.
|
||||
// A non nil error is returned in case of failure.
|
||||
// The main function, test functions and benchmark functions are internally compiled but not
|
||||
@@ -483,129 +597,22 @@ func (interp *Interpreter) Symbols(importPath string) Exports {
|
||||
return m
|
||||
}
|
||||
|
||||
func isFile(path string) bool {
|
||||
fi, err := os.Stat(path)
|
||||
func isFile(filesystem fs.FS, path string) bool {
|
||||
fi, err := fs.Stat(filesystem, path)
|
||||
return err == nil && fi.Mode().IsRegular()
|
||||
}
|
||||
|
||||
func (interp *Interpreter) eval(src, name string, inc bool) (res reflect.Value, err error) {
|
||||
if name != "" {
|
||||
interp.name = name
|
||||
}
|
||||
if interp.name == "" {
|
||||
interp.name = DefaultSourceName
|
||||
}
|
||||
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
var pc [64]uintptr // 64 frames should be enough.
|
||||
n := runtime.Callers(1, pc[:])
|
||||
err = Panic{Value: r, Callers: pc[:n], Stack: debug.Stack()}
|
||||
}
|
||||
}()
|
||||
|
||||
// Parse source to AST.
|
||||
pkgName, root, err := interp.ast(src, interp.name, inc)
|
||||
if err != nil || root == nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
if interp.astDot {
|
||||
dotCmd := interp.dotCmd
|
||||
if dotCmd == "" {
|
||||
dotCmd = defaultDotCmd(interp.name, "yaegi-ast-")
|
||||
}
|
||||
root.astDot(dotWriter(dotCmd), interp.name)
|
||||
if interp.noRun {
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
// Perform global types analysis.
|
||||
if err = interp.gtaRetry([]*node{root}, pkgName); err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Annotate AST with CFG informations.
|
||||
initNodes, err := interp.cfg(root, pkgName)
|
||||
prog, err := interp.compileSrc(src, name, inc)
|
||||
if err != nil {
|
||||
if interp.cfgDot {
|
||||
dotCmd := interp.dotCmd
|
||||
if dotCmd == "" {
|
||||
dotCmd = defaultDotCmd(interp.name, "yaegi-cfg-")
|
||||
}
|
||||
root.cfgDot(dotWriter(dotCmd))
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
if root.kind != fileStmt {
|
||||
// REPL may skip package statement.
|
||||
setExec(root.start)
|
||||
}
|
||||
interp.mutex.Lock()
|
||||
gs := interp.scopes[pkgName]
|
||||
if interp.universe.sym[pkgName] == nil {
|
||||
// Make the package visible under a path identical to its name.
|
||||
interp.srcPkg[pkgName] = gs.sym
|
||||
interp.universe.sym[pkgName] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT, path: pkgName}}
|
||||
interp.pkgNames[pkgName] = pkgName
|
||||
}
|
||||
interp.mutex.Unlock()
|
||||
|
||||
// Add main to list of functions to run, after all inits.
|
||||
if m := gs.sym[mainID]; pkgName == mainID && m != nil {
|
||||
initNodes = append(initNodes, m.node)
|
||||
}
|
||||
|
||||
if interp.cfgDot {
|
||||
dotCmd := interp.dotCmd
|
||||
if dotCmd == "" {
|
||||
dotCmd = defaultDotCmd(interp.name, "yaegi-cfg-")
|
||||
}
|
||||
root.cfgDot(dotWriter(dotCmd))
|
||||
}
|
||||
|
||||
if interp.noRun {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Generate node exec closures.
|
||||
if err = genRun(root); err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Init interpreter execution memory frame.
|
||||
interp.frame.setrunid(interp.runid())
|
||||
interp.frame.mutex.Lock()
|
||||
interp.resizeFrame()
|
||||
interp.frame.mutex.Unlock()
|
||||
|
||||
// Execute node closures.
|
||||
interp.run(root, nil)
|
||||
|
||||
// Wire and execute global vars.
|
||||
n, err := genGlobalVars([]*node{root}, interp.scopes[pkgName])
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
interp.run(n, nil)
|
||||
|
||||
for _, n := range initNodes {
|
||||
interp.run(n, interp.frame)
|
||||
}
|
||||
v := genValue(root)
|
||||
res = v(interp.frame)
|
||||
|
||||
// If result is an interpreter node, wrap it in a runtime callable function.
|
||||
if res.IsValid() {
|
||||
if n, ok := res.Interface().(*node); ok {
|
||||
res = genFunctionWrapper(n)(interp.frame)
|
||||
}
|
||||
}
|
||||
|
||||
return res, err
|
||||
return interp.Execute(prog)
|
||||
}
|
||||
|
||||
// EvalWithContext evaluates Go code represented as a string. It returns
|
||||
@@ -684,17 +691,17 @@ func (interp *Interpreter) Use(values Exports) error {
|
||||
// Checks if input values correspond to stdlib packages by looking for one
|
||||
// well known stdlib package path.
|
||||
if _, ok := values["fmt/fmt"]; ok {
|
||||
fixStdio(interp)
|
||||
fixStdlib(interp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fixStdio redefines interpreter stdlib symbols to use the standard input,
|
||||
// fixStdlib redefines interpreter stdlib symbols to use the standard input,
|
||||
// output and errror assigned to the interpreter. The changes are limited to
|
||||
// the interpreter only.
|
||||
// Note that it is possible to escape the virtualized stdio by
|
||||
// read/write directly to file descriptors 0, 1, 2.
|
||||
func fixStdio(interp *Interpreter) {
|
||||
func fixStdlib(interp *Interpreter) {
|
||||
p := interp.binPkg["fmt"]
|
||||
if p == nil {
|
||||
return
|
||||
@@ -739,6 +746,7 @@ func fixStdio(interp *Interpreter) {
|
||||
}
|
||||
|
||||
if p = interp.binPkg["os"]; p != nil {
|
||||
p["Args"] = reflect.ValueOf(&interp.args).Elem()
|
||||
if interp.specialStdio {
|
||||
// Inherit streams from interpreter even if they do not have a file descriptor.
|
||||
p["Stdin"] = reflect.ValueOf(&stdin).Elem()
|
||||
@@ -756,6 +764,27 @@ func fixStdio(interp *Interpreter) {
|
||||
p["Stderr"] = reflect.ValueOf(&s).Elem()
|
||||
}
|
||||
}
|
||||
if !interp.unrestricted {
|
||||
// In restricted mode, scripts can only access to a passed virtualized env, and can not write the real one.
|
||||
getenv := func(key string) string { return interp.env[key] }
|
||||
p["Clearenv"] = reflect.ValueOf(func() { interp.env = map[string]string{} })
|
||||
p["ExpandEnv"] = reflect.ValueOf(func(s string) string { return os.Expand(s, getenv) })
|
||||
p["Getenv"] = reflect.ValueOf(getenv)
|
||||
p["LookupEnv"] = reflect.ValueOf(func(key string) (s string, ok bool) { s, ok = interp.env[key]; return })
|
||||
p["Setenv"] = reflect.ValueOf(func(key, value string) error { interp.env[key] = value; return nil })
|
||||
p["Unsetenv"] = reflect.ValueOf(func(key string) error { delete(interp.env, key); return nil })
|
||||
p["Environ"] = reflect.ValueOf(func() (a []string) {
|
||||
for k, v := range interp.env {
|
||||
a = append(a, k+"="+v)
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if p = interp.binPkg["math/bits"]; p != nil {
|
||||
// Do not trust extracted value maybe from another arch.
|
||||
p["UintSize"] = reflect.ValueOf(constant.MakeInt64(bits.UintSize))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package interp_test
|
||||
|
||||
import (
|
||||
"go/build"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -20,13 +20,13 @@ func TestInterpConsistencyBuild(t *testing.T) {
|
||||
}
|
||||
dir := filepath.Join("..", "_test", "tmp")
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
if err := os.Mkdir(dir, 0700); err != nil {
|
||||
if err := os.Mkdir(dir, 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
baseDir := filepath.Join("..", "_test")
|
||||
files, err := ioutil.ReadDir(baseDir)
|
||||
files, err := os.ReadDir(baseDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -51,11 +51,14 @@ func TestInterpConsistencyBuild(t *testing.T) {
|
||||
file.Name() == "init1.go" || // expect error
|
||||
file.Name() == "io0.go" || // use random number
|
||||
file.Name() == "issue-1093.go" || // expect error
|
||||
file.Name() == "issue-1276.go" || // expect error
|
||||
file.Name() == "issue-1330.go" || // expect error
|
||||
file.Name() == "op1.go" || // expect error
|
||||
file.Name() == "op7.go" || // expect error
|
||||
file.Name() == "op9.go" || // expect error
|
||||
file.Name() == "bltn0.go" || // expect error
|
||||
file.Name() == "method16.go" || // private struct field
|
||||
file.Name() == "method39.go" || // expect error
|
||||
file.Name() == "switch8.go" || // expect error
|
||||
file.Name() == "switch9.go" || // expect error
|
||||
file.Name() == "switch13.go" || // expect error
|
||||
@@ -98,7 +101,16 @@ func TestInterpConsistencyBuild(t *testing.T) {
|
||||
file.Name() == "server1.go" || // syntax parsing
|
||||
file.Name() == "server0.go" || // syntax parsing
|
||||
file.Name() == "server.go" || // syntax parsing
|
||||
file.Name() == "range9.go" { // expect error
|
||||
file.Name() == "range9.go" || // expect error
|
||||
file.Name() == "unsafe6.go" || // needs go.mod to be 1.17
|
||||
file.Name() == "unsafe7.go" || // needs go.mod to be 1.17
|
||||
file.Name() == "type27.go" || // expect error
|
||||
file.Name() == "type28.go" || // expect error
|
||||
file.Name() == "type29.go" || // expect error
|
||||
file.Name() == "type30.go" || // expect error
|
||||
file.Name() == "type31.go" || // expect error
|
||||
file.Name() == "type32.go" || // expect error
|
||||
file.Name() == "type33.go" { // expect error
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -134,7 +146,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
|
||||
if err = w.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
outInterp, err := ioutil.ReadAll(r)
|
||||
outInterp, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -218,12 +230,12 @@ func TestInterpErrorConsistency(t *testing.T) {
|
||||
},
|
||||
{
|
||||
fileName: "issue-1093.go",
|
||||
expectedInterp: "9:6: cannot use type string as type int in assignment",
|
||||
expectedInterp: "9:6: cannot use type untyped string as type int in assignment",
|
||||
expectedExec: `9:4: cannot use "a" + b() (type string) as type int in assignment`,
|
||||
},
|
||||
{
|
||||
fileName: "op1.go",
|
||||
expectedInterp: "5:2: invalid operation: mismatched types int and float64",
|
||||
expectedInterp: "5:2: invalid operation: mismatched types int and untyped float",
|
||||
expectedExec: "5:4: constant 1.3 truncated to integer",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -6,12 +6,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -42,8 +42,8 @@ func TestEvalArithmetic(t *testing.T) {
|
||||
{desc: "add_FI", src: "2.3 + 3", res: "5.3"},
|
||||
{desc: "add_IF", src: "2 + 3.3", res: "5.3"},
|
||||
{desc: "add_SS", src: `"foo" + "bar"`, res: "foobar"},
|
||||
{desc: "add_SI", src: `"foo" + 1`, err: "1:28: invalid operation: mismatched types string and int"},
|
||||
{desc: "sub_SS", src: `"foo" - "bar"`, err: "1:28: invalid operation: operator - not defined on string"},
|
||||
{desc: "add_SI", src: `"foo" + 1`, err: "1:28: invalid operation: mismatched types untyped string and untyped int"},
|
||||
{desc: "sub_SS", src: `"foo" - "bar"`, err: "1:28: invalid operation: operator - not defined on untyped string"},
|
||||
{desc: "sub_II", src: "7 - 3", res: "4"},
|
||||
{desc: "sub_FI", src: "7.2 - 3", res: "4.2"},
|
||||
{desc: "sub_IF", src: "7 - 3.2", res: "3.8"},
|
||||
@@ -51,23 +51,23 @@ func TestEvalArithmetic(t *testing.T) {
|
||||
{desc: "mul_FI", src: "2.2 * 3", res: "6.6"},
|
||||
{desc: "mul_IF", src: "3 * 2.2", res: "6.6"},
|
||||
{desc: "quo_Z", src: "3 / 0", err: "1:28: invalid operation: division by zero"},
|
||||
{desc: "rem_FI", src: "8.2 % 4", err: "1:28: invalid operation: operator % not defined on float64"},
|
||||
{desc: "rem_FI", src: "8.2 % 4", err: "1:28: invalid operation: operator % not defined on untyped float"},
|
||||
{desc: "rem_Z", src: "8 % 0", err: "1:28: invalid operation: division by zero"},
|
||||
{desc: "shl_II", src: "1 << 8", res: "256"},
|
||||
{desc: "shl_IN", src: "1 << -1", err: "1:28: invalid operation: shift count type int, must be integer"},
|
||||
{desc: "shl_IN", src: "1 << -1", err: "1:28: invalid operation: shift count type untyped int, must be integer"},
|
||||
{desc: "shl_IF", src: "1 << 1.0", res: "2"},
|
||||
{desc: "shl_IF1", src: "1 << 1.1", err: "1:28: invalid operation: shift count type float64, must be integer"},
|
||||
{desc: "shl_IF1", src: "1 << 1.1", err: "1:28: invalid operation: shift count type untyped float, must be integer"},
|
||||
{desc: "shl_IF2", src: "1.0 << 1", res: "2"},
|
||||
{desc: "shr_II", src: "1 >> 8", res: "0"},
|
||||
{desc: "shr_IN", src: "1 >> -1", err: "1:28: invalid operation: shift count type int, must be integer"},
|
||||
{desc: "shr_IN", src: "1 >> -1", err: "1:28: invalid operation: shift count type untyped int, must be integer"},
|
||||
{desc: "shr_IF", src: "1 >> 1.0", res: "0"},
|
||||
{desc: "shr_IF1", src: "1 >> 1.1", err: "1:28: invalid operation: shift count type float64, must be integer"},
|
||||
{desc: "shr_IF1", src: "1 >> 1.1", err: "1:28: invalid operation: shift count type untyped float, must be integer"},
|
||||
{desc: "neg_I", src: "-2", res: "-2"},
|
||||
{desc: "pos_I", src: "+2", res: "2"},
|
||||
{desc: "bitnot_I", src: "^2", res: "-3"},
|
||||
{desc: "bitnot_F", src: "^0.2", err: "1:28: invalid operation: operator ^ not defined on float64"},
|
||||
{desc: "bitnot_F", src: "^0.2", err: "1:28: invalid operation: operator ^ not defined on untyped float"},
|
||||
{desc: "not_B", src: "!false", res: "true"},
|
||||
{desc: "not_I", src: "!0", err: "1:28: invalid operation: operator ! not defined on int"},
|
||||
{desc: "not_I", src: "!0", err: "1:28: invalid operation: operator ! not defined on untyped int"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ func TestEvalShift(t *testing.T) {
|
||||
i := interp.New(interp.Options{})
|
||||
runTests(t, i, []testCase{
|
||||
{src: "a, b, m := uint32(1), uint32(2), uint32(0); m = a + (1 << b)", res: "5"},
|
||||
{src: "c := uint(1); d := uint(+(-(1 << c)))", res: "18446744073709551614"},
|
||||
{src: "c := uint(1); d := uint64(+(-(1 << c)))", res: "18446744073709551614"},
|
||||
{src: "e, f := uint32(0), uint32(0); f = 1 << -(e * 2)", res: "1"},
|
||||
{src: "p := uint(0xdead); byte((1 << (p & 7)) - 1)", res: "31"},
|
||||
{pre: func() { eval(t, i, "const k uint = 1 << 17") }, src: "int(k)", res: "131072"},
|
||||
@@ -118,10 +118,10 @@ func TestEvalAssign(t *testing.T) {
|
||||
|
||||
runTests(t, i, []testCase{
|
||||
{src: `a := "Hello"; a += " world"`, res: "Hello world"},
|
||||
{src: `b := "Hello"; b += 1`, err: "1:42: invalid operation: mismatched types string and int"},
|
||||
{src: `b := "Hello"; b += 1`, err: "1:42: invalid operation: mismatched types string and untyped int"},
|
||||
{src: `c := "Hello"; c -= " world"`, err: "1:42: invalid operation: operator -= not defined on string"},
|
||||
{src: "e := 64.4; e %= 64", err: "1:39: invalid operation: operator %= not defined on float64"},
|
||||
{src: "f := int64(3.2)", err: "1:39: cannot convert expression of type float64 to type int64"},
|
||||
{src: "f := int64(3.2)", err: "1:39: cannot convert expression of type untyped float to type int64"},
|
||||
{src: "g := 1; g <<= 8", res: "256"},
|
||||
{src: "h := 1; h >>= 8", res: "0"},
|
||||
{src: "i := 1; j := &i; (*j) = 2", res: "2"},
|
||||
@@ -138,7 +138,9 @@ func TestEvalBuiltin(t *testing.T) {
|
||||
{src: `c := []int{1}; d := []int{2, 3}; c = append(c, d...); c`, res: "[1 2 3]"},
|
||||
{src: `string(append([]byte("hello "), "world"...))`, res: "hello world"},
|
||||
{src: `e := "world"; string(append([]byte("hello "), e...))`, res: "hello world"},
|
||||
{src: `b := []int{1}; b = append(1, 2, 3); b`, err: "1:54: first argument to append must be slice; have int"},
|
||||
{src: `b := []int{1}; b = append(1, 2, 3); b`, err: "1:54: first argument to append must be slice; have untyped int"},
|
||||
{src: `a1 := []int{0,1,2}; append(a1)`, res: "[0 1 2]"},
|
||||
{src: `append(nil)`, err: "first argument to append must be slice; have nil"},
|
||||
{src: `g := len(a)`, res: "1"},
|
||||
{src: `g := cap(a)`, res: "1"},
|
||||
{src: `g := len("test")`, res: "4"},
|
||||
@@ -160,10 +162,10 @@ func TestEvalBuiltin(t *testing.T) {
|
||||
{src: `k := []int{3, 4}; copy(k, []int{1,2}); k`, res: "[1 2]"},
|
||||
{src: `f := []byte("Hello"); copy(f, "world"); string(f)`, res: "world"},
|
||||
{src: `copy(g, g)`, err: "1:28: copy expects slice arguments"},
|
||||
{src: `copy(a, "world")`, err: "1:28: arguments to copy have different element types []int and string"},
|
||||
{src: `copy(a, "world")`, err: "1:28: arguments to copy have different element types []int and untyped string"},
|
||||
{src: `l := map[string]int{"a": 1, "b": 2}; delete(l, "a"); l`, res: "map[b:2]"},
|
||||
{src: `delete(a, 1)`, err: "1:35: first argument to delete must be map; have []int"},
|
||||
{src: `l := map[string]int{"a": 1, "b": 2}; delete(l, 1)`, err: "1:75: cannot use int as type string in delete"},
|
||||
{src: `l := map[string]int{"a": 1, "b": 2}; delete(l, 1)`, err: "1:75: cannot use untyped int as type string in delete"},
|
||||
{src: `a := []int{1,2}; println(a...)`, err: "invalid use of ... with builtin println"},
|
||||
{src: `m := complex(3, 2); real(m)`, res: "3"},
|
||||
{src: `m := complex(3, 2); imag(m)`, res: "2"},
|
||||
@@ -171,6 +173,10 @@ func TestEvalBuiltin(t *testing.T) {
|
||||
{src: `imag("test")`, err: "1:33: cannot convert \"test\" to complex128"},
|
||||
{src: `imag(a)`, err: "1:33: invalid argument type []int for imag"},
|
||||
{src: `real(a)`, err: "1:33: invalid argument type []int for real"},
|
||||
{src: `t := map[int]int{}; t[123]++; t`, res: "map[123:1]"},
|
||||
{src: `t := map[int]int{}; t[123]--; t`, res: "map[123:-1]"},
|
||||
{src: `t := map[int]int{}; t[123] += 1; t`, res: "map[123:1]"},
|
||||
{src: `t := map[int]int{}; t[123] -= 1; t`, res: "map[123:-1]"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -411,6 +417,8 @@ func TestEvalComparison(t *testing.T) {
|
||||
{src: `2 > 1`, res: "true"},
|
||||
{src: `1.2 > 1.1`, res: "true"},
|
||||
{src: `"hhh" > "ggg"`, res: "true"},
|
||||
{src: `a, b, c := 1, 1, false; if a == b { c = true }; c`, res: "true"},
|
||||
{src: `a, b, c := 1, 2, false; if a != b { c = true }; c`, res: "true"},
|
||||
{
|
||||
desc: "mismatched types",
|
||||
src: `
|
||||
@@ -433,11 +441,12 @@ func TestEvalCompositeArray(t *testing.T) {
|
||||
{src: "a := []int{1, 2, 7: 20, 30}", res: "[1 2 0 0 0 0 0 20 30]"},
|
||||
{src: `a := []int{1, 1.2}`, err: "1:42: 6/5 truncated to int"},
|
||||
{src: `a := []int{0:1, 0:1}`, err: "1:46: duplicate index 0 in array or slice literal"},
|
||||
{src: `a := []int{1.1:1, 1.2:"test"}`, err: "1:39: index float64 must be integer constant"},
|
||||
{src: `a := []int{1.1:1, 1.2:"test"}`, err: "1:39: index untyped float must be integer constant"},
|
||||
{src: `a := [2]int{1, 1.2}`, err: "1:43: 6/5 truncated to int"},
|
||||
{src: `a := [1]int{1, 2}`, err: "1:43: index 1 is out of bounds (>= 1)"},
|
||||
{src: `b := [l]int{1, 2}`, res: "[1 2 0 0 0 0 0 0 0 0]"},
|
||||
{src: `i := 10; a := [i]int{1, 2}`, err: "1:43: non-constant array bound \"i\""},
|
||||
{src: `c := [...]float64{1, 3: 3.4, 5}`, res: "[1 0 0 3.4 5]"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -500,7 +509,7 @@ func TestEvalConversion(t *testing.T) {
|
||||
{src: `a := uint64(1)`, res: "1"},
|
||||
{src: `i := 1.1; a := uint64(i)`, res: "1"},
|
||||
{src: `b := string(49)`, res: "1"},
|
||||
{src: `c := uint64(1.1)`, err: "1:40: cannot convert expression of type float64 to type uint64"},
|
||||
{src: `c := uint64(1.1)`, err: "1:40: cannot convert expression of type untyped float to type uint64"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -629,7 +638,7 @@ func TestEvalCall(t *testing.T) {
|
||||
a := test([]int{1}...)`, err: "2:10: invalid use of ..., corresponding parameter is non-variadic"},
|
||||
{src: ` test := func(a ...int) int { return 1 }
|
||||
blah := func() (int, int) { return 1, 1 }
|
||||
a := test(blah()...)`, err: "3:15: cannot use ... with 2-valued func()(int,int)"},
|
||||
a := test(blah()...)`, err: "3:15: cannot use ... with 2-valued func() (int,int)"},
|
||||
{src: ` test := func(a, b int) int { return a }
|
||||
blah := func() (int, int) { return 1, 1 }
|
||||
a := test(blah())`, res: "1"},
|
||||
@@ -638,10 +647,10 @@ func TestEvalCall(t *testing.T) {
|
||||
a := test(blah(), blah())`, res: "1"},
|
||||
{src: ` test := func(a, b, c, d int) int { return a }
|
||||
blah := func() (int, int) { return 1, 1 }
|
||||
a := test(blah(), blah())`, err: "3:15: cannot use func()(int,int) as type int"},
|
||||
a := test(blah(), blah())`, err: "3:15: cannot use func() (int,int) as type int"},
|
||||
{src: ` test := func(a, b int) int { return a }
|
||||
blah := func() (int, float64) { return 1, 1.1 }
|
||||
a := test(blah())`, err: "3:15: cannot use func()(int,float64) as type (int,int)"},
|
||||
a := test(blah())`, err: "3:15: cannot use func() (int,float64) as type (int,int)"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -875,7 +884,7 @@ func TestMultiEval(t *testing.T) {
|
||||
if err = w.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
outInterp, err := ioutil.ReadAll(r)
|
||||
outInterp, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -906,7 +915,7 @@ func TestMultiEvalNoName(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for k, v := range names {
|
||||
data, err := ioutil.ReadFile(filepath.Join(f.Name(), v))
|
||||
data, err := os.ReadFile(filepath.Join(f.Name(), v))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -924,6 +933,84 @@ func TestMultiEvalNoName(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
const goMinorVersionTest = 16
|
||||
|
||||
func TestHasIOFS(t *testing.T) {
|
||||
code := `
|
||||
// +build go1.16
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pe := fs.PathError{}
|
||||
pe.Op = "nothing"
|
||||
pe.Path = "/nowhere"
|
||||
pe.Err = errors.New("an error")
|
||||
println(pe.Error())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// nothing /nowhere: an error
|
||||
`
|
||||
|
||||
var buf bytes.Buffer
|
||||
i := interp.New(interp.Options{Stdout: &buf})
|
||||
if err := i.Use(interp.Symbols); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := i.Use(stdlib.Symbols); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := i.Eval(code); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var expectedOutput string
|
||||
var minor int
|
||||
var err error
|
||||
version := runtime.Version()
|
||||
fields := strings.Fields(version)
|
||||
// Go stable
|
||||
if len(fields) == 1 {
|
||||
v := strings.Split(version, ".")
|
||||
if len(v) < 2 {
|
||||
t.Fatalf("unexpected: %v", version)
|
||||
}
|
||||
minor, err = strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
// Go devel
|
||||
if fields[0] != "devel" {
|
||||
t.Fatalf("unexpected: %v", fields[0])
|
||||
}
|
||||
parts := strings.Split(fields[1], "-")
|
||||
if len(parts) != 2 {
|
||||
t.Fatalf("unexpected: %v", fields[1])
|
||||
}
|
||||
minor, err = strconv.Atoi(strings.TrimPrefix(parts[0], "go1."))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if minor >= goMinorVersionTest {
|
||||
expectedOutput = "nothing /nowhere: an error\n"
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
if buf.String() != expectedOutput {
|
||||
t.Fatalf("got: %v, wanted: %v", output, expectedOutput)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportPathIsKey(t *testing.T) {
|
||||
// No need to check the results of Eval, as TestFile already does it.
|
||||
i := interp.New(interp.Options{GoPath: filepath.FromSlash("../_test/testdata/redeclaration-global7")})
|
||||
@@ -1627,3 +1714,38 @@ func TestIssue1151(t *testing.T) {
|
||||
{src: "x := pkg.Array{1}", res: "[1]"},
|
||||
})
|
||||
}
|
||||
|
||||
func TestPassArgs(t *testing.T) {
|
||||
i := interp.New(interp.Options{Args: []string{"arg0", "arg1"}})
|
||||
if err := i.Use(stdlib.Symbols); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
i.ImportUsed()
|
||||
runTests(t, i, []testCase{
|
||||
{src: "os.Args", res: "[arg0 arg1]"},
|
||||
})
|
||||
}
|
||||
|
||||
func TestRestrictedEnv(t *testing.T) {
|
||||
i := interp.New(interp.Options{Env: []string{"foo=bar"}})
|
||||
if err := i.Use(stdlib.Symbols); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
i.ImportUsed()
|
||||
runTests(t, i, []testCase{
|
||||
{src: `os.Getenv("foo")`, res: "bar"},
|
||||
{src: `s, ok := os.LookupEnv("foo"); s`, res: "bar"},
|
||||
{src: `s, ok := os.LookupEnv("foo"); ok`, res: "true"},
|
||||
{src: `s, ok := os.LookupEnv("PATH"); s`, res: ""},
|
||||
{src: `s, ok := os.LookupEnv("PATH"); ok`, res: "false"},
|
||||
{src: `os.Setenv("foo", "baz"); os.Environ()`, res: "[foo=baz]"},
|
||||
{src: `os.ExpandEnv("foo is ${foo}")`, res: "foo is baz"},
|
||||
{src: `os.Unsetenv("foo"); os.Environ()`, res: "[]"},
|
||||
{src: `os.Setenv("foo", "baz"); os.Environ()`, res: "[foo=baz]"},
|
||||
{src: `os.Clearenv(); os.Environ()`, res: "[]"},
|
||||
{src: `os.Setenv("foo", "baz"); os.Environ()`, res: "[foo=baz]"},
|
||||
})
|
||||
if s, ok := os.LookupEnv("foo"); ok {
|
||||
t.Fatal("expected \"\", got " + s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -26,7 +25,7 @@ func TestFile(t *testing.T) {
|
||||
_ = os.Setenv("YAEGI_SPECIAL_STDIO", "1")
|
||||
|
||||
baseDir := filepath.Join("..", "_test")
|
||||
files, err := ioutil.ReadDir(baseDir)
|
||||
files, err := os.ReadDir(baseDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
325
interp/op.go
325
interp/op.go
@@ -1490,6 +1490,13 @@ func addAssign(n *node) {
|
||||
next := getExec(n.tnext)
|
||||
typ := n.typ.TypeOf()
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
setMap := isMapEntry(c0)
|
||||
var mapValue, indexValue func(*frame) reflect.Value
|
||||
|
||||
if setMap {
|
||||
mapValue = genValue(c0.child[0])
|
||||
indexValue = genValue(c0.child[1])
|
||||
}
|
||||
|
||||
if c1.rval.IsValid() {
|
||||
switch typ.Kind() {
|
||||
@@ -1499,6 +1506,9 @@ func addAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, s := v0(f)
|
||||
v.SetString(s + v1)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
@@ -1507,6 +1517,9 @@ func addAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetInt(i + j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -1515,6 +1528,9 @@ func addAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetUint(i + j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
@@ -1523,6 +1539,9 @@ func addAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetFloat(i + j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
@@ -1531,6 +1550,9 @@ func addAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v := v0(f)
|
||||
v.SetComplex(v.Complex() + v1)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -1542,6 +1564,9 @@ func addAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, s := v0(f)
|
||||
v.SetString(s + v1(f).String())
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
@@ -1551,6 +1576,9 @@ func addAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetInt(i + j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -1560,6 +1588,9 @@ func addAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetUint(i + j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
@@ -1569,6 +1600,9 @@ func addAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetFloat(i + j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
@@ -1577,6 +1611,9 @@ func addAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v := v0(f)
|
||||
v.SetComplex(v.Complex() + v1(f).Complex())
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -1587,6 +1624,13 @@ func andAssign(n *node) {
|
||||
next := getExec(n.tnext)
|
||||
typ := n.typ.TypeOf()
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
setMap := isMapEntry(c0)
|
||||
var mapValue, indexValue func(*frame) reflect.Value
|
||||
|
||||
if setMap {
|
||||
mapValue = genValue(c0.child[0])
|
||||
indexValue = genValue(c0.child[1])
|
||||
}
|
||||
|
||||
if c1.rval.IsValid() {
|
||||
switch typ.Kind() {
|
||||
@@ -1596,6 +1640,9 @@ func andAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetInt(i & j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -1604,6 +1651,9 @@ func andAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetUint(i & j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -1616,6 +1666,9 @@ func andAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetInt(i & j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -1625,6 +1678,9 @@ func andAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetUint(i & j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -1635,6 +1691,13 @@ func andNotAssign(n *node) {
|
||||
next := getExec(n.tnext)
|
||||
typ := n.typ.TypeOf()
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
setMap := isMapEntry(c0)
|
||||
var mapValue, indexValue func(*frame) reflect.Value
|
||||
|
||||
if setMap {
|
||||
mapValue = genValue(c0.child[0])
|
||||
indexValue = genValue(c0.child[1])
|
||||
}
|
||||
|
||||
if c1.rval.IsValid() {
|
||||
switch typ.Kind() {
|
||||
@@ -1644,6 +1707,9 @@ func andNotAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetInt(i &^ j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -1652,6 +1718,9 @@ func andNotAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetUint(i &^ j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -1664,6 +1733,9 @@ func andNotAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetInt(i &^ j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -1673,6 +1745,9 @@ func andNotAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetUint(i &^ j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -1683,6 +1758,13 @@ func mulAssign(n *node) {
|
||||
next := getExec(n.tnext)
|
||||
typ := n.typ.TypeOf()
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
setMap := isMapEntry(c0)
|
||||
var mapValue, indexValue func(*frame) reflect.Value
|
||||
|
||||
if setMap {
|
||||
mapValue = genValue(c0.child[0])
|
||||
indexValue = genValue(c0.child[1])
|
||||
}
|
||||
|
||||
if c1.rval.IsValid() {
|
||||
switch typ.Kind() {
|
||||
@@ -1692,6 +1774,9 @@ func mulAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetInt(i * j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -1700,6 +1785,9 @@ func mulAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetUint(i * j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
@@ -1708,6 +1796,9 @@ func mulAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetFloat(i * j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
@@ -1716,6 +1807,9 @@ func mulAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v := v0(f)
|
||||
v.SetComplex(v.Complex() * v1)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -1728,6 +1822,9 @@ func mulAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetInt(i * j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -1737,6 +1834,9 @@ func mulAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetUint(i * j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
@@ -1746,6 +1846,9 @@ func mulAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetFloat(i * j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
@@ -1754,6 +1857,9 @@ func mulAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v := v0(f)
|
||||
v.SetComplex(v.Complex() * v1(f).Complex())
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -1764,6 +1870,13 @@ func orAssign(n *node) {
|
||||
next := getExec(n.tnext)
|
||||
typ := n.typ.TypeOf()
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
setMap := isMapEntry(c0)
|
||||
var mapValue, indexValue func(*frame) reflect.Value
|
||||
|
||||
if setMap {
|
||||
mapValue = genValue(c0.child[0])
|
||||
indexValue = genValue(c0.child[1])
|
||||
}
|
||||
|
||||
if c1.rval.IsValid() {
|
||||
switch typ.Kind() {
|
||||
@@ -1773,6 +1886,9 @@ func orAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetInt(i | j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -1781,6 +1897,9 @@ func orAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetUint(i | j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -1793,6 +1912,9 @@ func orAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetInt(i | j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -1802,6 +1924,9 @@ func orAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetUint(i | j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -1812,6 +1937,13 @@ func quoAssign(n *node) {
|
||||
next := getExec(n.tnext)
|
||||
typ := n.typ.TypeOf()
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
setMap := isMapEntry(c0)
|
||||
var mapValue, indexValue func(*frame) reflect.Value
|
||||
|
||||
if setMap {
|
||||
mapValue = genValue(c0.child[0])
|
||||
indexValue = genValue(c0.child[1])
|
||||
}
|
||||
|
||||
if c1.rval.IsValid() {
|
||||
switch typ.Kind() {
|
||||
@@ -1821,6 +1953,9 @@ func quoAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetInt(i / j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -1829,6 +1964,9 @@ func quoAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetUint(i / j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
@@ -1837,6 +1975,9 @@ func quoAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetFloat(i / j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
@@ -1845,6 +1986,9 @@ func quoAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v := v0(f)
|
||||
v.SetComplex(v.Complex() / v1)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -1857,6 +2001,9 @@ func quoAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetInt(i / j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -1866,6 +2013,9 @@ func quoAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetUint(i / j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
@@ -1875,6 +2025,9 @@ func quoAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetFloat(i / j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
@@ -1883,6 +2036,9 @@ func quoAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v := v0(f)
|
||||
v.SetComplex(v.Complex() / v1(f).Complex())
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -1893,6 +2049,13 @@ func remAssign(n *node) {
|
||||
next := getExec(n.tnext)
|
||||
typ := n.typ.TypeOf()
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
setMap := isMapEntry(c0)
|
||||
var mapValue, indexValue func(*frame) reflect.Value
|
||||
|
||||
if setMap {
|
||||
mapValue = genValue(c0.child[0])
|
||||
indexValue = genValue(c0.child[1])
|
||||
}
|
||||
|
||||
if c1.rval.IsValid() {
|
||||
switch typ.Kind() {
|
||||
@@ -1902,6 +2065,9 @@ func remAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetInt(i % j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -1910,6 +2076,9 @@ func remAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetUint(i % j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -1922,6 +2091,9 @@ func remAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetInt(i % j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -1931,6 +2103,9 @@ func remAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetUint(i % j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -1941,6 +2116,13 @@ func shlAssign(n *node) {
|
||||
next := getExec(n.tnext)
|
||||
typ := n.typ.TypeOf()
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
setMap := isMapEntry(c0)
|
||||
var mapValue, indexValue func(*frame) reflect.Value
|
||||
|
||||
if setMap {
|
||||
mapValue = genValue(c0.child[0])
|
||||
indexValue = genValue(c0.child[1])
|
||||
}
|
||||
|
||||
if c1.rval.IsValid() {
|
||||
switch typ.Kind() {
|
||||
@@ -1950,6 +2132,9 @@ func shlAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetInt(i << j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -1958,6 +2143,9 @@ func shlAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetUint(i << j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -1970,6 +2158,9 @@ func shlAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetInt(i << j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -1979,6 +2170,9 @@ func shlAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetUint(i << j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -1989,6 +2183,13 @@ func shrAssign(n *node) {
|
||||
next := getExec(n.tnext)
|
||||
typ := n.typ.TypeOf()
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
setMap := isMapEntry(c0)
|
||||
var mapValue, indexValue func(*frame) reflect.Value
|
||||
|
||||
if setMap {
|
||||
mapValue = genValue(c0.child[0])
|
||||
indexValue = genValue(c0.child[1])
|
||||
}
|
||||
|
||||
if c1.rval.IsValid() {
|
||||
switch typ.Kind() {
|
||||
@@ -1998,6 +2199,9 @@ func shrAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetInt(i >> j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -2006,6 +2210,9 @@ func shrAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetUint(i >> j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -2018,6 +2225,9 @@ func shrAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetInt(i >> j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -2027,6 +2237,9 @@ func shrAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetUint(i >> j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -2037,6 +2250,13 @@ func subAssign(n *node) {
|
||||
next := getExec(n.tnext)
|
||||
typ := n.typ.TypeOf()
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
setMap := isMapEntry(c0)
|
||||
var mapValue, indexValue func(*frame) reflect.Value
|
||||
|
||||
if setMap {
|
||||
mapValue = genValue(c0.child[0])
|
||||
indexValue = genValue(c0.child[1])
|
||||
}
|
||||
|
||||
if c1.rval.IsValid() {
|
||||
switch typ.Kind() {
|
||||
@@ -2046,6 +2266,9 @@ func subAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetInt(i - j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -2054,6 +2277,9 @@ func subAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetUint(i - j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
@@ -2062,6 +2288,9 @@ func subAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetFloat(i - j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
@@ -2070,6 +2299,9 @@ func subAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v := v0(f)
|
||||
v.SetComplex(v.Complex() - v1)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -2082,6 +2314,9 @@ func subAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetInt(i - j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -2091,6 +2326,9 @@ func subAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetUint(i - j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
@@ -2100,6 +2338,9 @@ func subAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetFloat(i - j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
@@ -2108,6 +2349,9 @@ func subAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v := v0(f)
|
||||
v.SetComplex(v.Complex() - v1(f).Complex())
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -2118,6 +2362,13 @@ func xorAssign(n *node) {
|
||||
next := getExec(n.tnext)
|
||||
typ := n.typ.TypeOf()
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
setMap := isMapEntry(c0)
|
||||
var mapValue, indexValue func(*frame) reflect.Value
|
||||
|
||||
if setMap {
|
||||
mapValue = genValue(c0.child[0])
|
||||
indexValue = genValue(c0.child[1])
|
||||
}
|
||||
|
||||
if c1.rval.IsValid() {
|
||||
switch typ.Kind() {
|
||||
@@ -2127,6 +2378,9 @@ func xorAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetInt(i ^ j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -2135,6 +2389,9 @@ func xorAssign(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetUint(i ^ j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -2147,6 +2404,9 @@ func xorAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetInt(i ^ j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
@@ -2156,6 +2416,9 @@ func xorAssign(n *node) {
|
||||
v, i := v0(f)
|
||||
_, j := v1(f)
|
||||
v.SetUint(i ^ j)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -2165,34 +2428,54 @@ func xorAssign(n *node) {
|
||||
func dec(n *node) {
|
||||
next := getExec(n.tnext)
|
||||
typ := n.typ.TypeOf()
|
||||
c0 := n.child[0]
|
||||
setMap := isMapEntry(c0)
|
||||
var mapValue, indexValue func(*frame) reflect.Value
|
||||
|
||||
if setMap {
|
||||
mapValue = genValue(c0.child[0])
|
||||
indexValue = genValue(c0.child[1])
|
||||
}
|
||||
|
||||
switch typ.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
v0 := genValueInt(n.child[0])
|
||||
v0 := genValueInt(c0)
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetInt(i - 1)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
v0 := genValueUint(n.child[0])
|
||||
v0 := genValueUint(c0)
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetUint(i - 1)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
v0 := genValueFloat(n.child[0])
|
||||
v0 := genValueFloat(c0)
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetFloat(i - 1)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
v0 := genValue(n.child[0])
|
||||
v0 := genValue(c0)
|
||||
n.exec = func(f *frame) bltn {
|
||||
v := v0(f)
|
||||
v.SetComplex(v.Complex() - 1)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -2201,34 +2484,54 @@ func dec(n *node) {
|
||||
func inc(n *node) {
|
||||
next := getExec(n.tnext)
|
||||
typ := n.typ.TypeOf()
|
||||
c0 := n.child[0]
|
||||
setMap := isMapEntry(c0)
|
||||
var mapValue, indexValue func(*frame) reflect.Value
|
||||
|
||||
if setMap {
|
||||
mapValue = genValue(c0.child[0])
|
||||
indexValue = genValue(c0.child[1])
|
||||
}
|
||||
|
||||
switch typ.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
v0 := genValueInt(n.child[0])
|
||||
v0 := genValueInt(c0)
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetInt(i + 1)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
v0 := genValueUint(n.child[0])
|
||||
v0 := genValueUint(c0)
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetUint(i + 1)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
v0 := genValueFloat(n.child[0])
|
||||
v0 := genValueFloat(c0)
|
||||
n.exec = func(f *frame) bltn {
|
||||
v, i := v0(f)
|
||||
v.SetFloat(i + 1)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
v0 := genValue(n.child[0])
|
||||
v0 := genValue(c0)
|
||||
n.exec = func(f *frame) bltn {
|
||||
v := v0(f)
|
||||
v.SetComplex(v.Complex() + 1)
|
||||
if setMap {
|
||||
mapValue(f).SetMapIndex(indexValue(f), v)
|
||||
}
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -2341,7 +2644,7 @@ func equal(n *node) {
|
||||
fnext := getExec(n.fnext)
|
||||
n.exec = func(f *frame) bltn {
|
||||
i1 := v1(f).Interface()
|
||||
if i0 != i1 {
|
||||
if i0 == i1 {
|
||||
dest(f).SetBool(true)
|
||||
return tnext
|
||||
}
|
||||
@@ -2363,7 +2666,7 @@ func equal(n *node) {
|
||||
fnext := getExec(n.fnext)
|
||||
n.exec = func(f *frame) bltn {
|
||||
i0 := v0(f).Interface()
|
||||
if i0 != i1 {
|
||||
if i0 == i1 {
|
||||
dest(f).SetBool(true)
|
||||
return tnext
|
||||
}
|
||||
@@ -2386,7 +2689,7 @@ func equal(n *node) {
|
||||
n.exec = func(f *frame) bltn {
|
||||
i0 := v0(f).Interface()
|
||||
i1 := v1(f).Interface()
|
||||
if i0 != i1 {
|
||||
if i0 == i1 {
|
||||
dest(f).SetBool(true)
|
||||
return tnext
|
||||
}
|
||||
|
||||
192
interp/program.go
Normal file
192
interp/program.go
Normal file
@@ -0,0 +1,192 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go/ast"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
// A Program is Go code that has been parsed and compiled.
|
||||
type Program struct {
|
||||
pkgName string
|
||||
root *node
|
||||
init []*node
|
||||
}
|
||||
|
||||
// Compile parses and compiles a Go code represented as a string.
|
||||
func (interp *Interpreter) Compile(src string) (*Program, error) {
|
||||
return interp.compileSrc(src, "", true)
|
||||
}
|
||||
|
||||
// CompilePath parses and compiles a Go code located at the given path.
|
||||
func (interp *Interpreter) CompilePath(path string) (*Program, error) {
|
||||
if !isFile(interp.filesystem, path) {
|
||||
_, err := interp.importSrc(mainID, path, NoTest)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return interp.compileSrc(string(b), path, false)
|
||||
}
|
||||
|
||||
func (interp *Interpreter) compileSrc(src, name string, inc bool) (*Program, error) {
|
||||
if name != "" {
|
||||
interp.name = name
|
||||
}
|
||||
if interp.name == "" {
|
||||
interp.name = DefaultSourceName
|
||||
}
|
||||
|
||||
// Parse source to AST.
|
||||
n, err := interp.parse(src, interp.name, inc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return interp.CompileAST(n)
|
||||
}
|
||||
|
||||
// CompileAST builds a Program for the given Go code AST. Files and block
|
||||
// statements can be compiled, as can most expressions. Var declaration nodes
|
||||
// cannot be compiled.
|
||||
func (interp *Interpreter) CompileAST(n ast.Node) (*Program, error) {
|
||||
// Convert AST.
|
||||
pkgName, root, err := interp.ast(n)
|
||||
if err != nil || root == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if interp.astDot {
|
||||
dotCmd := interp.dotCmd
|
||||
if dotCmd == "" {
|
||||
dotCmd = defaultDotCmd(interp.name, "yaegi-ast-")
|
||||
}
|
||||
root.astDot(dotWriter(dotCmd), interp.name)
|
||||
if interp.noRun {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Perform global types analysis.
|
||||
if err = interp.gtaRetry([]*node{root}, pkgName, pkgName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Annotate AST with CFG informations.
|
||||
initNodes, err := interp.cfg(root, nil, pkgName, pkgName)
|
||||
if err != nil {
|
||||
if interp.cfgDot {
|
||||
dotCmd := interp.dotCmd
|
||||
if dotCmd == "" {
|
||||
dotCmd = defaultDotCmd(interp.name, "yaegi-cfg-")
|
||||
}
|
||||
root.cfgDot(dotWriter(dotCmd))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if root.kind != fileStmt {
|
||||
// REPL may skip package statement.
|
||||
setExec(root.start)
|
||||
}
|
||||
interp.mutex.Lock()
|
||||
gs := interp.scopes[pkgName]
|
||||
if interp.universe.sym[pkgName] == nil {
|
||||
// Make the package visible under a path identical to its name.
|
||||
interp.srcPkg[pkgName] = gs.sym
|
||||
interp.universe.sym[pkgName] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT, path: pkgName}}
|
||||
interp.pkgNames[pkgName] = pkgName
|
||||
}
|
||||
interp.mutex.Unlock()
|
||||
|
||||
// Add main to list of functions to run, after all inits.
|
||||
if m := gs.sym[mainID]; pkgName == mainID && m != nil {
|
||||
initNodes = append(initNodes, m.node)
|
||||
}
|
||||
|
||||
if interp.cfgDot {
|
||||
dotCmd := interp.dotCmd
|
||||
if dotCmd == "" {
|
||||
dotCmd = defaultDotCmd(interp.name, "yaegi-cfg-")
|
||||
}
|
||||
root.cfgDot(dotWriter(dotCmd))
|
||||
}
|
||||
|
||||
return &Program{pkgName, root, initNodes}, nil
|
||||
}
|
||||
|
||||
// Execute executes compiled Go code.
|
||||
func (interp *Interpreter) Execute(p *Program) (res reflect.Value, err error) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
var pc [64]uintptr // 64 frames should be enough.
|
||||
n := runtime.Callers(1, pc[:])
|
||||
err = Panic{Value: r, Callers: pc[:n], Stack: debug.Stack()}
|
||||
}
|
||||
}()
|
||||
|
||||
// Generate node exec closures.
|
||||
if err = genRun(p.root); err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Init interpreter execution memory frame.
|
||||
interp.frame.setrunid(interp.runid())
|
||||
interp.frame.mutex.Lock()
|
||||
interp.resizeFrame()
|
||||
interp.frame.mutex.Unlock()
|
||||
|
||||
// Execute node closures.
|
||||
interp.run(p.root, nil)
|
||||
|
||||
// Wire and execute global vars.
|
||||
n, err := genGlobalVars([]*node{p.root}, interp.scopes[p.pkgName])
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
interp.run(n, nil)
|
||||
|
||||
for _, n := range p.init {
|
||||
interp.run(n, interp.frame)
|
||||
}
|
||||
v := genValue(p.root)
|
||||
res = v(interp.frame)
|
||||
|
||||
// If result is an interpreter node, wrap it in a runtime callable function.
|
||||
if res.IsValid() {
|
||||
if n, ok := res.Interface().(*node); ok {
|
||||
res = genFunctionWrapper(n)(interp.frame)
|
||||
}
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// ExecuteWithContext executes compiled Go code.
|
||||
func (interp *Interpreter) ExecuteWithContext(ctx context.Context, p *Program) (res reflect.Value, err error) {
|
||||
interp.mutex.Lock()
|
||||
interp.done = make(chan struct{})
|
||||
interp.cancelChan = !interp.opt.fastChan
|
||||
interp.mutex.Unlock()
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
res, err = interp.Execute(p)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
interp.stop()
|
||||
return reflect.Value{}, ctx.Err()
|
||||
case <-done:
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user