Compare commits

...

31 Commits

Author SHA1 Message Date
Marc Vertes
513f5e37aa interp: fix type conversion for constant expressions
The case of a constant arithmetic expression being of float kind because
of quotient was not handled correctly. Simplify constant extraction to
reflect.Value first, then conversion to target type, and let reflect Convert
method panic if conversion is not valid.

Fixes #920.
2020-10-23 10:44:04 +02:00
Marc Vertes
9520a92241 interp: fix type in assign of shift operations
Type checking on shift operands was failing for untyped variable values.

Fix propagation of type in assignment. Optimize assignment of arithmetic
operations on variables by skipping the assign and writing directly to
destination frame value in the operator function.

Skip some slow tests when given -short test option.

Fixes #917.
2020-10-23 10:32:03 +02:00
rsteube
d47821bfaa interp: add indexExpr check for reflect.Array
In typecheck.go, type check was failing for expressions like `[2]int{0, 1}[0:]`.
```
cannot slice type [2]int
```

Fixes #914 .
2020-10-21 23:00:04 +02:00
rsteube
d7ede8ed5c interp: added sliceExpr and starExpr
Type check was failing for expression such as: `&(*tree)[node:][0]`, as in

```go
tree, _ := huffmanTreePool.Get().(*[]huffmanTree)
...

initHuffmanTree(&(*tree)[node:][0], histogram[l], -1, int16(l))
```

see c3da72aa01/brotli_bit_stream.go (L469)
2020-10-21 18:14:03 +02:00
Marc Vertes
22c63b225c interp: fix array size definition in case of forward declared constant
In type.go, the symbol lookup on constant id was not performed. Handle
the ident kind explicitely to allow that.

Fixes #911.
2020-10-21 13:08:03 +02:00
mpl
c0eaab0891 interp: fix assignable check
The assignable check used to be too strict as it lacked the property that
if an untyped const can be represented as a T, then it is assignable to T.

And we can now use that fixed check to add a missing check: in a return
statement, we now make sure that any of the returned elements are
assignable to what the signature tells us they should be.
2020-10-21 10:06:03 +02:00
rsteube
c74d050c5a interp: added kind to log for typecheck (#910) 2020-10-21 09:46:05 +02:00
Marc Vertes
804664c631 interp: fix type check in constant unary operations
In unary constant operations, the test for unsigned was defeated by
testing for int first, which is true also for unsigned. Make sure that
testing for unsigned precedes testing for int.

Fixes #907.
2020-10-19 10:32:03 +02:00
Kamil Samigullin
f6d8261a8a chore: add homebrew-tap recipe
Publish homebrew-tap recipe to https://github.com/traefik/homebrew-tap
2020-10-17 05:54:04 +02:00
Marc Vertes
68c02ce054 feature: expose extract package, previously internal 2020-10-15 18:58:03 +02:00
Marc Vertes
4b3e9ee231 feature: use environment in addition to flags to control test options
This applies to -syscall, -unsafe and -unrestricted flags with the
corresponding env variables YAEGI_SYSCALL, YAEGI_UNSAFE and
YAEGI_UNRESTRICTED, already used in the same way for the run
sub-command.
2020-10-15 18:42:04 +02:00
mpl
8916618a81 interp: API change for Symbols method
Often enough when debugging, one does not know exactly what argument
should be given to Symbols, as it's not always clear in which
scope/namespace the symbol one is looking for is stored in.

Therefore, this change enables the Symbols method to now take the empty
string as an argument, which results in all the known symbols to be
returned (keyed by import path).

As a consequence, when an non-empty argument is given, the returned
result should be similar to what we had before, except it is now
returned as the sole entry of an encompassing map.

In addition, the "binary" symbols (i.e. the ones ingested through a
Use call), are now also taken into account.
2020-10-15 10:02:04 +02:00
mpl
57b49f40d6 interp: make Symbols method take into account type objects too 2020-10-14 17:44:09 +02:00
Marc Vertes
190dade469 fix: detect a wrong number of arguments at return
Also fix error ouptut in the run command to avoid displaying twice
the same error.

Fixes #819.
2020-10-14 17:30:04 +02:00
Marc Vertes
6b652ea485 feature: provide a version sub-command
A version sub-command is implemented.
A local install target is added to Makefile, to set version from git at build.

Fixes #840.
2020-10-14 16:24:05 +02:00
Marc Vertes
473bc63588 fix: behavior of select and time ticker
There was several issues:
- access to field on pointer to struct from runtime: fix in
  lookupBinField
- assign operation was skipped when performed in a comm clause
- the direction of comm clause was wrong if a channel send operation was
  performed in a body of a receive comm clause

Fixes #884.
2020-10-14 15:50:04 +02:00
Marc Vertes
e32da38ad0 fix: append from / to runtime defined array
Check first for runtime defined array (typ.cat of valueT)
to avoid checking inexisting properties (an panic), when
deciding to use appendSlice or not.

Fixes #880.
2020-10-13 17:14:04 +02:00
Marc Vertes
b2b519c2fd fix: correctly return constant expressions in functions
In the case of a function returning a single value, a constant
result could be ignored, and the function would return a zero value.

Fixes #889.
2020-10-13 17:02:04 +02:00
Marc Vertes
9491e58920 fix: correct handling of select comm clause with empty body
Fixes #887.
2020-10-13 11:24:04 +02:00
Marc Vertes
f362237ac5 fix: perform constant type conversion when required
Fixes #893.
2020-10-12 19:06:03 +02:00
Marc Vertes
a83ec1f925 fix: allow yaegi command to interpret itself
Since the introduction of restricted stdlib and syscall symbols, the
capability of yaegi to interpret itself was broken.
The use of unrestricted symbols is now also controlled by environment
variables, to allow propagation accross nested interpreters.
The interpreter Panic symbol was not wrapped, this is fixed now.
the import path resolution was failing if the working directory was
outside of GOPATH.
The documentation and readme have been ajusted.

Fixes #890.
2020-10-09 11:48:04 +02:00
mpl
155ca4e6ad interp: support implicit-type slice of pointers of struct composite literal
Also fix same case for implicit-type maps of etc.

Fixes #883
2020-10-09 10:32:03 +02:00
mpl
ca196a5768 interp: fix implicit type for bin composite lit case
When the type is implicit, the first element in the list of children is
not the type of the composite literal, but it is one of the actual
children, so it should not be discarded.

Fixes #862
2020-10-05 16:42:03 +02:00
Marc Vertes
b78d55c66b fix: compute array type size from constant expression
Fixes #875.
2020-10-05 16:12:04 +02:00
mpl
16f5586a11 interp: apply integer division when appropriate
When working with an untyped const expression involving a division, if
the default type of the result should be an int (for example because the
default types of all the operands are ints as well), then we should make
sure that the operation that is applied is indeed an integer division,
and that the type of the result is not a float.

This is achieved by using the QUO_ASSIGN operator, instead of the QUO
operator.

This should fix several problems lurking around, and it notably fixes
one of the visible consequences, which is a systematic panic when using
the REPL as a "calculator".

This incidentally also allows us to revert what was done in
5dfc3b86dc since it now turns out it was
just a hack to fix one of the symptoms.

Fixes #864
2020-10-05 10:50:03 +02:00
Julien Breux
c3cf301c60 fix: replace playground link in docs 2020-09-30 10:26:03 +02:00
Marc Vertes
0dde990d0b feature: improve extract, add unrestricted syscalls 2020-09-29 18:42:05 +02:00
Marc Vertes
4cfeb1946e fix: concurrent eval test 2020-09-29 15:22:04 +02:00
Nicholas Wiersma
ec64b006cf fix: convert struct tags properly 2020-09-29 09:22:04 +02:00
Nicholas Wiersma
f36d4e01eb fix: yaegi os.Args should contain the script name 2020-09-28 10:54:05 +02:00
mpl
5dfc3b86dc interp: fix division for const 2020-09-28 10:42:05 +02:00
504 changed files with 4371 additions and 2289 deletions

2
.gitignore vendored
View File

@@ -3,7 +3,7 @@
*.dot
.idea/
/yaegi
cmd/goexports/goexports
internal/cmd/extract/extract
example/inception/inception
_test/tmp/
/dist

View File

@@ -26,31 +26,6 @@ builds:
- goos: darwin
goarch: 386
- id: goexports
binary: goexports
main: ./cmd/goexports/
goos:
- darwin
- linux
# - windows
- freebsd
- openbsd
- solaris
goarch:
- amd64
- 386
- arm
- arm64
goarm:
- 7
- 6
- 5
ignore:
- goos: darwin
goarch: 386
changelog:
sort: asc
filters:
@@ -70,3 +45,19 @@ archives:
format: zip
files:
- LICENSE
brews:
- github:
owner: traefik
name: homebrew-tap
commit_author:
name: traefiker
email: 30906710+traefiker@users.noreply.github.com
folder: Formula
homepage: https://github.com/traefik/yaegi
description: |
Yaegi is Another Elegant Go Interpreter.
It powers executable Go scripts and plugins, in embedded interpreters
or interactive shells, on top of the Go runtime.
test: |
system "#{bin}/yaegi version"

View File

@@ -3,25 +3,28 @@ check:
golangci-lint run
# Generate stdlib/syscall/syscall_GOOS_GOARCH.go for all platforms
gen_all_syscall: cmd/goexports/goexports
@cd stdlib/syscall && \
for v in $$(go tool dist list); do \
gen_all_syscall: internal/cmd/extract/extract
@for v in $$(go tool dist list); do \
echo syscall_$${v%/*}_$${v#*/}.go; \
GOOS=$${v%/*} GOARCH=$${v#*/} go generate; \
GOOS=$${v%/*} GOARCH=$${v#*/} go generate ./stdlib/syscall ./stdlib/unrestricted; \
done
cmd/goexports/goexports: cmd/goexports/goexports.go
go generate cmd/goexports/goexports.go
internal/cmd/extract/extract:
rm -f internal/cmd/extract/extract
go generate ./internal/cmd/extract
generate: gen_all_syscall
go generate
install:
GOFLAGS=-ldflags=-X=main.version=$$(git describe --tags) go install ./...
tests:
GO111MODULE=off go test -v ./...
GO111MODULE=off go test -race ./interp
go test -v ./...
go test -race ./interp
# https://github.com/goreleaser/godownloader
generate_downloader:
godownloader --repo=traefik/yaegi -o install.sh
install.sh: .goreleaser.yml
godownloader --repo=traefik/yaegi -o install.sh .goreleaser.yml
.PHONY: check gen_all_syscall gen_tests generate_downloader
.PHONY: check gen_all_syscall gen_tests generate_downloader internal/cmd/extract/extract install

View File

@@ -74,7 +74,7 @@ func main() {
}
```
[Go Playground](https://play.golang.org/p/zzvw4VlerLP)
[Go Playground](https://play.golang.org/p/2n-EpZbMYI9)
### As a dynamic extension framework
@@ -112,7 +112,7 @@ func main() {
}
```
[Go Playground](https://play.golang.org/p/6SEAoaO7n0U)
[Go Playground](https://play.golang.org/p/WvwH4JqrU-p)
### As a command-line interpreter
@@ -128,10 +128,20 @@ Hello World
>
```
Or interpret Go files:
Note that in interactive mode, all stdlib package are pre-imported,
you can use them directly:
```console
$ yaegi cmd/yaegi/yaegi.go
$ yaegi
> reflect.TypeOf(time.Date)
: func(int, time.Month, int, int, int, int, int, *time.Location) time.Time
>
```
Or interpret Go packages, directories or files, including itself:
```console
$ yaegi -syscall -unsafe -unrestricted github.com/traefik/yaegi/cmd/yaegi
>
```

17
_test/a43.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "fmt"
type T [l1 + l2]int
const (
l1 = 2
l2 = 3
)
func main() {
fmt.Println(T{})
}
// Output:
// [0 0 0 0 0]

12
_test/a44.go Normal file
View File

@@ -0,0 +1,12 @@
package main
var a = [max]int{}
const max = 32
func main() {
println(len(a))
}
// Output:
// 32

22
_test/addr1.go Normal file
View File

@@ -0,0 +1,22 @@
package main
import "fmt"
type T struct {
A int
B int
}
func main() {
a := &[]T{
{1, 2},
{3, 4},
}
fmt.Println("a:", a)
x := &(*a)[1:][0]
fmt.Println("x:", x)
}
// Output:
// a: &[{1 2} {3 4}]
// x: &{3 4}

15
_test/append1.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import (
"bufio"
"bytes"
)
func main() {
s := bufio.NewScanner(bytes.NewReader([]byte("Hello\nTest\nLine3")))
s.Scan()
println(string(append(s.Bytes(), []byte(" World")...)))
}
// Output:
// Hello World

15
_test/append2.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import (
"bufio"
"bytes"
)
func main() {
s := bufio.NewScanner(bytes.NewReader([]byte("Hello\nTest\nLine3")))
s.Scan()
println(string(append(s.Bytes(), " World"...)))
}
// Output:
// Hello World

15
_test/assign16.go Normal file
View File

@@ -0,0 +1,15 @@
package main
type H struct {
bits uint
}
func main() {
h := &H{8}
var x uint = (1 << h.bits) >> 6
println(x)
}
// Output:
// 4

View File

@@ -0,0 +1,17 @@
package main
import (
"fmt"
"image"
)
func main() {
v := map[string]*image.Point{
"foo": {X: 3, Y: 2},
"bar": {X: 4, Y: 5},
}
fmt.Println(v["foo"], v["bar"])
}
// Output:
// (3,2) (4,5)

View File

@@ -0,0 +1,17 @@
package main
import (
"fmt"
"image"
)
func main() {
v := []*image.Point{
{X: 3, Y: 2},
{X: 4, Y: 5},
}
fmt.Println(v)
}
// Output:
// [(3,2) (4,5)]

16
_test/binstruct_slice0.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"fmt"
"image"
)
func main() {
v := []image.Point{
{X: 3, Y: 2},
}
fmt.Println(v)
}
// Output:
// [(3,2)]

12
_test/const16.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import (
"fmt"
)
func main() {
fmt.Println(7 / 3)
}
// Output:
// 2

14
_test/const17.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
var t [7/3]int
func main() {
t[0] = 3/2
t[1] = 5/2
fmt.Println(t)
}
// Output:
// [1 2]

11
_test/const18.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import "time"
func main() {
a := int64(time.Second)
println(a)
}
// Output:
// 1000000000

17
_test/const19.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import (
"fmt"
"time"
)
func get10Hours() time.Duration {
return 10 * time.Hour
}
func main() {
fmt.Println(get10Hours().String())
}
// Output:
// 10h0m0s

12
_test/const20.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import "fmt"
const maxLen = int64(int(^uint(0) >> 1))
func main() {
fmt.Println(maxLen)
}
// Output:
// 9223372036854775807

12
_test/const21.go Normal file
View File

@@ -0,0 +1,12 @@
package main
const a = 64
var b uint = a * a / 2
func main() {
println(b)
}
// Output:
// 2048

10
_test/fun23.go Normal file
View File

@@ -0,0 +1,10 @@
package main
func f(x int) { return x }
func main() {
print("hello")
}
// Error:
// 3:17: too many arguments to return

10
_test/fun24.go Normal file
View File

@@ -0,0 +1,10 @@
package main
func f(x int) (int, int) { return x, "foo" }
func main() {
print("hello")
}
// Error:
// cannot use "foo" (type stringT) as type intT in return argument

10
_test/fun25.go Normal file
View File

@@ -0,0 +1,10 @@
package main
func f(x string) (a int, b int) { return x, 5 }
func main() {
print("hello")
}
// Error:
// cannot use x (type stringT) as type intT in return argument

23
_test/issue-880.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
"bufio"
"bytes"
)
func main() {
var buf1 = make([]byte, 1024)
var buf2 []byte
buf1 = []byte("Hallo\nTest\nLine3")
s := bufio.NewScanner(bytes.NewReader(buf1))
for s.Scan() {
buf2 = append(buf2, append(s.Bytes(), []byte("\n")...)...)
}
print(string(buf2))
}
// Output:
// Hallo
// Test
// Line3

View File

@@ -1,18 +1,20 @@
package main
import "time"
import "fmt"
import (
"fmt"
"time"
)
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
time.Sleep(1e9)
time.Sleep(1e7)
c1 <- "one"
}()
go func() {
time.Sleep(2e9)
time.Sleep(2e7)
c2 <- "two"
}()

44
_test/select14.go Normal file
View File

@@ -0,0 +1,44 @@
package main
import (
"fmt"
"time"
)
const (
period = 100 * time.Millisecond
precision = 5 * time.Millisecond
)
func main() {
counter := 0
p := time.Now()
ticker := time.NewTicker(period)
ch := make(chan int)
go func() {
for i := 0; i < 3; i++ {
select {
case t := <-ticker.C:
counter = counter + 1
ch <- counter
if d := t.Sub(p) - period; d < -precision || d > precision {
fmt.Println("wrong delay", d)
}
p = t
}
}
ch <- 0
}()
for c := range ch {
if c == 0 {
break
}
println(c)
}
}
// Output:
// 1
// 2
// 3

23
_test/select15.go Normal file
View File

@@ -0,0 +1,23 @@
package main
type T struct {
c1 chan string
c2 chan string
}
func main() {
t := &T{}
t.c2 = make(chan string)
go func(c chan string) { c <- "done" }(t.c2)
select {
case msg := <-t.c1:
println("received from c1:", msg)
case <-t.c2:
}
println("Bye")
}
// Output:
// Bye

11
_test/slice.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import "fmt"
func main() {
a := [2][2]int{{0, 1}, {2, 3}}
fmt.Println(a[0][0:])
}
// Output:
// [0 1]

24
_test/struct58.go Normal file
View File

@@ -0,0 +1,24 @@
package main
import (
"fmt"
"reflect"
)
type A struct {
Test string `tag:"test"`
}
func main() {
a := A{}
t := reflect.TypeOf(a)
f, ok := t.FieldByName("Test")
if !ok {
return
}
fmt.Println(f.Tag.Get("tag"))
}
// Output:
// test

View File

@@ -1,137 +0,0 @@
//go:generate go build
/*
Goexports generates wrappers of package exported symbols.
Output files are written in the current directory, and prefixed with the go version.
Usage:
goexports package...
Example:
goexports github.com/traefik/yaegi/interp
The same goexport program is used for all target operating systems and architectures.
The GOOS and GOARCH environment variables set the desired target.
*/
package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"io"
"log"
"os"
"path"
"runtime"
"strings"
"github.com/traefik/yaegi/extract"
)
// genLicense generates the correct LICENSE header text from the provided
// path to a LICENSE file.
func genLicense(fname string) (string, error) {
if fname == "" {
return "", nil
}
f, err := os.Open(fname)
if err != nil {
return "", fmt.Errorf("could not open LICENSE file: %v", err)
}
defer func() { _ = f.Close() }()
license := new(strings.Builder)
sc := bufio.NewScanner(f)
for sc.Scan() {
txt := sc.Text()
if txt != "" {
txt = " " + txt
}
license.WriteString("//" + txt + "\n")
}
if sc.Err() != nil {
return "", fmt.Errorf("could not scan LICENSE file: %v", err)
}
return license.String(), nil
}
var (
licenseFlag = flag.String("license", "", "path to a LICENSE file")
// TODO: deal with a module that has several packages (so there's only one go.mod file at the root of the project).
importPathFlag = flag.String("import_path", "", "the namespace for the symbols extracted from the argument. Not needed if the argument is from the stdlib, or if the name can be found in a go.mod")
)
func main() {
flag.Parse()
if flag.NArg() == 0 {
flag.Usage()
log.Fatalf("missing package path")
}
license, err := genLicense(*licenseFlag)
if err != nil {
log.Fatal(err)
}
wd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
ext := extract.Extractor{
Dest: path.Base(wd),
License: license,
}
goos, goarch := os.Getenv("GOOS"), os.Getenv("GOARCH")
skip := map[string]bool{}
if goos == "solaris" {
skip["syscall.RawSyscall6"] = true
skip["syscall.Syscall6"] = true
}
ext.Skip = skip
for _, pkgIdent := range flag.Args() {
var buf bytes.Buffer
importPath, err := ext.Extract(pkgIdent, *importPathFlag, &buf)
if err != nil {
log.Println(err)
continue
}
var oFile string
if pkgIdent == "syscall" {
oFile = strings.ReplaceAll(importPath, "/", "_") + "_" + goos + "_" + goarch + ".go"
} else {
oFile = strings.ReplaceAll(importPath, "/", "_") + ".go"
}
prefix := runtime.Version()
if runtime.Version() != "devel" {
parts := strings.Split(runtime.Version(), ".")
prefix = parts[0] + "_" + extract.GetMinor(parts[1])
}
f, err := os.Create(prefix + "_" + oFile)
if err != nil {
log.Fatal(err)
}
if _, err := io.Copy(f, &buf); err != nil {
_ = f.Close()
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
}
}

View File

@@ -15,11 +15,15 @@ import (
func extractCmd(arg []string) error {
var licensePath string
var importPath string
var name string
var exclude string
var include string
eflag := flag.NewFlagSet("run", flag.ContinueOnError)
eflag.StringVar(&licensePath, "license", "", "path to a LICENSE file")
eflag.StringVar(&importPath, "import_path", "", "the namespace for the extracted symbols")
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.Usage = func() {
fmt.Println("Usage: yaegi extract [options] packages...")
fmt.Println("Options:")
@@ -45,20 +49,32 @@ func extractCmd(arg []string) error {
return err
}
if name == "" {
name = path.Base(wd)
}
ext := extract.Extractor{
Dest: path.Base(wd),
Dest: name,
License: license,
}
if exclude != "" {
ext.Exclude = strings.Split(exclude, ",")
}
if include != "" {
ext.Include = strings.Split(include, ",")
}
r := strings.NewReplacer("/", "-", ".", "_")
for _, pkgIdent := range args {
var buf bytes.Buffer
importPath, err := ext.Extract(pkgIdent, importPath, &buf)
importPath, err := ext.Extract(pkgIdent, name, &buf)
if err != nil {
fmt.Fprintln(os.Stderr, err)
continue
}
oFile := strings.ReplaceAll(importPath, "/", "_") + ".go"
oFile := r.Replace(importPath) + ".go"
f, err := os.Create(oFile)
if err != nil {
return err

View File

@@ -14,6 +14,7 @@ The commands are:
help print usage information
run execute a Go program from source
test execute test functions in a Go package
version print version
Use "yaegi help <command>" for more information about a command.
@@ -37,6 +38,9 @@ func help(arg []string) error {
return run([]string{"-h"})
case Test:
return test([]string{"-h"})
case Version:
fmt.Println("Usage: yaegi version")
return nil
default:
return fmt.Errorf("help: invalid yaegi command: %v", cmd)
}

View File

@@ -6,6 +6,7 @@ import (
"go/build"
"io/ioutil"
"os"
"strconv"
"strings"
"github.com/traefik/yaegi/interp"
@@ -17,19 +18,21 @@ import (
func run(arg []string) error {
var interactive bool
var useSyscall bool
var useUnrestricted bool
var useUnsafe bool
var tags string
var cmd string
var err error
// The following flags are initialized from environment.
useSyscall, _ := strconv.ParseBool(os.Getenv("YAEGI_SYSCALL"))
useUnrestricted, _ := strconv.ParseBool(os.Getenv("YAEGI_UNRESTRICTED"))
useUnsafe, _ := strconv.ParseBool(os.Getenv("YAEGI_UNSAFE"))
rflag := flag.NewFlagSet("run", flag.ContinueOnError)
rflag.BoolVar(&interactive, "i", false, "start an interactive REPL")
rflag.BoolVar(&useSyscall, "syscall", false, "include syscall symbols")
rflag.BoolVar(&useUnrestricted, "unrestricted", false, "include unrestricted symbols")
rflag.BoolVar(&useSyscall, "syscall", useSyscall, "include syscall symbols")
rflag.BoolVar(&useUnrestricted, "unrestricted", useUnrestricted, "include unrestricted symbols")
rflag.StringVar(&tags, "tags", "", "set a list of build tags")
rflag.BoolVar(&useUnsafe, "unsafe", false, "include usafe symbols")
rflag.BoolVar(&useUnsafe, "unsafe", useUnsafe, "include unsafe symbols")
rflag.StringVar(&cmd, "e", "", "set the command to be executed (instead of script or/and shell)")
rflag.Usage = func() {
fmt.Println("Usage: yaegi run [options] [path] [args]")
@@ -46,31 +49,40 @@ func run(arg []string) error {
i.Use(interp.Symbols)
if useSyscall {
i.Use(syscall.Symbols)
// Using a environment var allows a nested interpreter to import the syscall package.
if err := os.Setenv("YAEGI_SYSCALL", "1"); err != nil {
return err
}
}
if useUnsafe {
i.Use(unsafe.Symbols)
if err := os.Setenv("YAEGI_UNSAFE", "1"); err != nil {
return err
}
}
if useUnrestricted {
// Use of unrestricted symbols should always follow use of stdlib symbols, to update them.
// Use of unrestricted symbols should always follow stdlib and syscall symbols, to update them.
i.Use(unrestricted.Symbols)
if err := os.Setenv("YAEGI_UNRESTRICTED", "1"); err != nil {
return err
}
}
if cmd != "" {
_, err = i.Eval(cmd)
showError(err)
}
if len(args) == 0 {
if interactive || cmd == "" {
_, err = i.REPL()
showError(err)
_, err = i.REPL()
}
return err
}
// Skip first os arg to set command line as expected by interpreted main
path := args[0]
os.Args = arg[1:]
os.Args = arg
flag.CommandLine = flag.NewFlagSet(path, flag.ExitOnError)
if isFile(path) {
@@ -78,7 +90,6 @@ func run(arg []string) error {
} else {
_, err = i.EvalPath(path)
}
showError(err)
if err != nil {
return err
@@ -86,7 +97,6 @@ func run(arg []string) error {
if interactive {
_, err = i.REPL()
showError(err)
}
return err
}

View File

@@ -1,11 +1,13 @@
package main
import (
"errors"
"flag"
"fmt"
"go/build"
"os"
"regexp"
"strconv"
"strings"
"testing"
@@ -18,22 +20,24 @@ import (
func test(arg []string) (err error) {
var (
bench string
benchmem bool
benchtime string
count string
cpu string
failfast bool
run string
short bool
tags string
useUnrestricted bool
useUnsafe bool
useSyscall bool
timeout string
verbose bool
bench string
benchmem bool
benchtime string
count string
cpu string
failfast bool
run string
short bool
tags string
timeout string
verbose bool
)
// The following flags are initialized from environment.
useSyscall, _ := strconv.ParseBool(os.Getenv("YAEGI_SYSCALL"))
useUnrestricted, _ := strconv.ParseBool(os.Getenv("YAEGI_UNRESTRICTED"))
useUnsafe, _ := strconv.ParseBool(os.Getenv("YAEGI_UNSAFE"))
tflag := flag.NewFlagSet("test", flag.ContinueOnError)
tflag.StringVar(&bench, "bench", "", "Run only those benchmarks matching a regular expression.")
tflag.BoolVar(&benchmem, "benchmem", false, "Print memory allocation statistics for benchmarks.")
@@ -45,9 +49,9 @@ func test(arg []string) (err error) {
tflag.BoolVar(&short, "short", false, "Tell long-running tests to shorten their run time.")
tflag.StringVar(&tags, "tags", "", "Set a list of build tags.")
tflag.StringVar(&timeout, "timeout", "", "If a test binary runs longer than duration d, panic.")
tflag.BoolVar(&useUnrestricted, "unrestricted", false, "Include unrestricted symbols.")
tflag.BoolVar(&useUnsafe, "unsafe", false, "Include usafe symbols.")
tflag.BoolVar(&useSyscall, "syscall", false, "Include syscall symbols.")
tflag.BoolVar(&useUnrestricted, "unrestricted", useUnrestricted, "Include unrestricted symbols.")
tflag.BoolVar(&useUnsafe, "unsafe", useUnsafe, "Include usafe symbols.")
tflag.BoolVar(&useSyscall, "syscall", useSyscall, "Include syscall symbols.")
tflag.BoolVar(&verbose, "v", false, "Verbose output: log all tests as they are run.")
tflag.Usage = func() {
fmt.Println("Usage: yaegi test [options] [path]")
@@ -104,12 +108,22 @@ func test(arg []string) (err error) {
i.Use(interp.Symbols)
if useSyscall {
i.Use(syscall.Symbols)
// Using a environment var allows a nested interpreter to import the syscall package.
if err := os.Setenv("YAEGI_SYSCALL", "1"); err != nil {
return err
}
}
if useUnrestricted {
i.Use(unrestricted.Symbols)
if err := os.Setenv("YAEGI_UNRESTRICTED", "1"); err != nil {
return err
}
}
if useUnsafe {
i.Use(unsafe.Symbols)
if err := os.Setenv("YAEGI_UNSAFE", "1"); err != nil {
return err
}
}
if err = i.EvalTest(path); err != nil {
return err
@@ -117,7 +131,11 @@ func test(arg []string) (err error) {
benchmarks := []testing.InternalBenchmark{}
tests := []testing.InternalTest{}
for name, sym := range i.Symbols(path) {
syms, ok := i.Symbols(path)[path]
if !ok {
return errors.New("No tests found")
}
for name, sym := range syms {
switch fun := sym.Interface().(type) {
case func(*testing.B):
benchmarks = append(benchmarks, testing.InternalBenchmark{name, fun})

View File

@@ -71,7 +71,16 @@ Options:
-unsafe
include unsafe symbols.
Debugging support (may be removed at any time):
Environment variables:
YAEGI_SYSCALL=1
Include syscall symbols (same as -syscall flag).
YAEGI_UNRESTRICTED=1
Include unrestricted symbols (same as -unrestricted flag).
YAEGI_UNSAFE=1
Include unsafe symbols (same as -unsafe flag).
YAEGI_PROMPT=1
Force enable the printing of the REPL prompt and the result of last instruction,
even if stdin is not a terminal.
YAEGI_AST_DOT=1
Generate and display graphviz dot of AST with dotty(1)
YAEGI_CFG_DOT=1
@@ -90,6 +99,8 @@ import (
"fmt"
"log"
"os"
"github.com/traefik/yaegi/interp"
)
const (
@@ -97,8 +108,11 @@ const (
Help = "help"
Run = "run"
Test = "test"
Version = "version"
)
var version = "devel" // This may be overwritten at build time.
func main() {
var cmd string
var err error
@@ -119,6 +133,8 @@ func main() {
err = run(os.Args[2:])
case Test:
err = test(os.Args[2:])
case Version:
fmt.Println(version)
default:
// If no command is given, fallback to default "run" command.
// This allows scripts starting with "#!/usr/bin/env yaegi",
@@ -129,8 +145,10 @@ func main() {
}
if err != nil && !errors.Is(err, flag.ErrHelp) {
err = fmt.Errorf("%s: %w", cmd, err)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, fmt.Errorf("%s: %w", cmd, err))
if p, ok := err.(interp.Panic); ok {
fmt.Fprintln(os.Stderr, string(p.Stack))
}
exitCode = 1
}
os.Exit(exitCode)

View File

@@ -18,13 +18,14 @@ import (
"os"
"path"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"text/template"
)
const model = `// Code generated by 'github.com/traefik/yaegi/extract {{.PkgName}}'. DO NOT EDIT.
const model = `// Code generated by 'yaegi extract {{.PkgName}}'. DO NOT EDIT.
{{.License}}
@@ -111,7 +112,17 @@ var restricted = map[string]bool{
"logNew": true,
}
func genContent(dest, importPath, license string, p *types.Package, skip map[string]bool) ([]byte, error) {
func matchList(name string, list []string) (match bool, err error) {
for _, re := range list {
match, err = regexp.MatchString(re, name)
if err != nil || match {
return
}
}
return
}
func (e *Extractor) genContent(importPath string, p *types.Package) ([]byte, error) {
prefix := "_" + importPath + "_"
prefix = strings.NewReplacer("/", "_", "-", "_", ".", "_").Replace(prefix)
@@ -137,11 +148,26 @@ func genContent(dest, importPath, license string, p *types.Package, skip map[str
continue
}
pname := path.Base(importPath) + "." + name
if skip[pname] {
if len(e.Include) > 0 {
match, err := matchList(name, e.Include)
if err != nil {
return nil, err
}
if !match {
// Explicitly defined include expressions force non matching symbols to be skipped.
continue
}
}
match, err := matchList(name, e.Exclude)
if err != nil {
return nil, err
}
if match {
continue
}
pname := path.Base(importPath) + "." + name
if rname := path.Base(importPath) + name; restricted[rname] {
// Restricted symbol, locally provided by stdlib wrapper.
pname = rname
@@ -150,7 +176,7 @@ func genContent(dest, importPath, license string, p *types.Package, skip map[str
switch o := o.(type) {
case *types.Const:
if b, ok := o.Type().(*types.Basic); ok && (b.Info()&types.IsUntyped) != 0 {
// convert untyped constant to right type to avoid overflow
// Convert untyped constant to right type to avoid overflow.
val[name] = Val{fixConst(pname, o.Val(), imports), false}
} else {
val[name] = Val{pname, false}
@@ -201,12 +227,18 @@ func genContent(dest, importPath, license string, p *types.Package, skip map[str
}
}
buildTags, err := buildTags()
if err != nil {
return nil, err
// Generate buildTags with Go version only for stdlib packages.
// Third party packages do not depend on Go compiler version by default.
var buildTags string
if isInStdlib(importPath) {
var err error
buildTags, err = genBuildTags()
if err != nil {
return nil, err
}
}
base := template.New("goexports")
base := template.New("extract")
parse, err := base.Parse(model)
if err != nil {
return nil, fmt.Errorf("template parsing error: %v", err)
@@ -231,14 +263,14 @@ func genContent(dest, importPath, license string, p *types.Package, skip map[str
b := new(bytes.Buffer)
data := map[string]interface{}{
"Dest": dest,
"Dest": e.Dest,
"Imports": imports,
"PkgName": importPath,
"Val": val,
"Typ": typ,
"Wrap": wrap,
"BuildTags": buildTags,
"License": license,
"License": e.License,
}
err = parse.Execute(b, data)
if err != nil {
@@ -288,12 +320,20 @@ func fixConst(name string, val constant.Value, imports map[string]bool) string {
return fmt.Sprintf("constant.MakeFromLiteral(%q, token.%s, 0)", str, tok)
}
// Extractor creates a package with all the symbols from a dependency package.
type Extractor struct {
Dest string // The name of the created package.
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.
}
// importPath checks whether pkgIdent is an existing directory relative to
// e.WorkingDir. If yes, it returns the actual import path of the Go package
// located in the directory. If it is definitely a relative path, but it does not
// exist, an error is returned. Otherwise, it is assumed to be an import path, and
// pkgIdent is returned.
func (e Extractor) importPath(pkgIdent, importPath string) (string, error) {
func (e *Extractor) importPath(pkgIdent, importPath string) (string, error) {
wd, err := os.Getwd()
if err != nil {
return "", err
@@ -353,20 +393,13 @@ func (e Extractor) importPath(pkgIdent, importPath string) (string, error) {
return parts[1], nil
}
// Extractor creates a package with all the symbols from a dependency package.
type Extractor struct {
Dest string // the name of the created package.
License string // license text to be included in the created package, optional.
Skip map[string]bool
}
// Extract writes to rw a Go package with all the symbols found at pkgIdent.
// pkgIdent can be an import path, or a local path, relative to e.WorkingDir. In
// the latter case, Extract returns the actual import path of the package found at
// pkgIdent, otherwise it just returns pkgIdent.
// If pkgIdent is an import path, it is looked up in GOPATH. Vendoring is not
// supported yet, and the behavior is only defined for GO111MODULE=off.
func (e Extractor) Extract(pkgIdent, importPath string, rw io.Writer) (string, error) {
func (e *Extractor) Extract(pkgIdent, importPath string, rw io.Writer) (string, error) {
ipp, err := e.importPath(pkgIdent, importPath)
if err != nil {
return "", err
@@ -377,7 +410,7 @@ func (e Extractor) Extract(pkgIdent, importPath string, rw io.Writer) (string, e
return "", err
}
content, err := genContent(e.Dest, ipp, e.License, pkg, e.Skip)
content, err := e.genContent(ipp, pkg)
if err != nil {
return "", err
}
@@ -403,7 +436,7 @@ func GetMinor(part string) string {
return minor
}
func buildTags() (string, error) {
func genBuildTags() (string, error) {
version := runtime.Version()
if version == "devel" {
return "", nil
@@ -423,3 +456,5 @@ func buildTags() (string, error) {
return currentGoVersion + ",!" + nextGoVersion, nil
}
func isInStdlib(path string) bool { return !strings.Contains(path, ".") }

View File

@@ -8,9 +8,7 @@ import (
"testing"
)
var expectedOutput = `// Code generated by 'github.com/traefik/yaegi/extract guthib.com/baz'. DO NOT EDIT.
// +build BUILD_TAGS
var expectedOutput = `// Code generated by 'yaegi extract guthib.com/baz'. DO NOT EDIT.
package bar
@@ -27,14 +25,6 @@ func init() {
}
`
func init() {
buildTags, err := buildTags()
if err != nil {
panic(err)
}
expectedOutput = strings.Replace(expectedOutput, "BUILD_TAGS", buildTags, 1)
}
func TestPackages(t *testing.T) {
testCases := []struct {
desc string

View File

@@ -1,7 +1,7 @@
package yaegi
//go:generate go generate github.com/traefik/yaegi/internal/cmd/extract
//go:generate go generate github.com/traefik/yaegi/interp
//go:generate go generate github.com/traefik/yaegi/cmd/goexports
//go:generate go generate github.com/traefik/yaegi/stdlib
//go:generate go generate github.com/traefik/yaegi/stdlib/syscall
//go:generate go generate github.com/traefik/yaegi/stdlib/unsafe

View File

@@ -1,6 +1,6 @@
#!/bin/sh
set -e
# Code generated by godownloader on 2020-09-19T15:21:58Z. DO NOT EDIT.
# Code generated by godownloader on 2020-09-27T12:53:27Z. DO NOT EDIT.
#
usage() {
@@ -62,35 +62,35 @@ execute() {
}
get_binaries() {
case "$PLATFORM" in
darwin/amd64) BINARIES="yaegi goexports" ;;
darwin/arm64) BINARIES="yaegi goexports" ;;
darwin/armv5) BINARIES="yaegi goexports" ;;
darwin/armv6) BINARIES="yaegi goexports" ;;
darwin/armv7) BINARIES="yaegi goexports" ;;
freebsd/386) BINARIES="yaegi goexports" ;;
freebsd/amd64) BINARIES="yaegi goexports" ;;
freebsd/arm64) BINARIES="yaegi goexports" ;;
freebsd/armv5) BINARIES="yaegi goexports" ;;
freebsd/armv6) BINARIES="yaegi goexports" ;;
freebsd/armv7) BINARIES="yaegi goexports" ;;
linux/386) BINARIES="yaegi goexports" ;;
linux/amd64) BINARIES="yaegi goexports" ;;
linux/arm64) BINARIES="yaegi goexports" ;;
linux/armv5) BINARIES="yaegi goexports" ;;
linux/armv6) BINARIES="yaegi goexports" ;;
linux/armv7) BINARIES="yaegi goexports" ;;
openbsd/386) BINARIES="yaegi goexports" ;;
openbsd/amd64) BINARIES="yaegi goexports" ;;
openbsd/arm64) BINARIES="yaegi goexports" ;;
openbsd/armv5) BINARIES="yaegi goexports" ;;
openbsd/armv6) BINARIES="yaegi goexports" ;;
openbsd/armv7) BINARIES="yaegi goexports" ;;
solaris/386) BINARIES="yaegi goexports" ;;
solaris/amd64) BINARIES="yaegi goexports" ;;
solaris/arm64) BINARIES="yaegi goexports" ;;
solaris/armv5) BINARIES="yaegi goexports" ;;
solaris/armv6) BINARIES="yaegi goexports" ;;
solaris/armv7) BINARIES="yaegi goexports" ;;
darwin/amd64) BINARIES="yaegi" ;;
darwin/arm64) BINARIES="yaegi" ;;
darwin/armv5) BINARIES="yaegi" ;;
darwin/armv6) BINARIES="yaegi" ;;
darwin/armv7) BINARIES="yaegi" ;;
freebsd/386) BINARIES="yaegi" ;;
freebsd/amd64) BINARIES="yaegi" ;;
freebsd/arm64) BINARIES="yaegi" ;;
freebsd/armv5) BINARIES="yaegi" ;;
freebsd/armv6) BINARIES="yaegi" ;;
freebsd/armv7) BINARIES="yaegi" ;;
linux/386) BINARIES="yaegi" ;;
linux/amd64) BINARIES="yaegi" ;;
linux/arm64) BINARIES="yaegi" ;;
linux/armv5) BINARIES="yaegi" ;;
linux/armv6) BINARIES="yaegi" ;;
linux/armv7) BINARIES="yaegi" ;;
openbsd/386) BINARIES="yaegi" ;;
openbsd/amd64) BINARIES="yaegi" ;;
openbsd/arm64) BINARIES="yaegi" ;;
openbsd/armv5) BINARIES="yaegi" ;;
openbsd/armv6) BINARIES="yaegi" ;;
openbsd/armv7) BINARIES="yaegi" ;;
solaris/386) BINARIES="yaegi" ;;
solaris/amd64) BINARIES="yaegi" ;;
solaris/arm64) BINARIES="yaegi" ;;
solaris/armv5) BINARIES="yaegi" ;;
solaris/armv6) BINARIES="yaegi" ;;
solaris/armv7) BINARIES="yaegi" ;;
*)
log_crit "platform $PLATFORM is not supported. Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new"
exit 1
@@ -250,8 +250,8 @@ uname_arch_check() {
untar() {
tarball=$1
case "${tarball}" in
*.tar.gz | *.tgz) tar -xzf "${tarball}" ;;
*.tar) tar -xf "${tarball}" ;;
*.tar.gz | *.tgz) tar --no-same-owner -xzf "${tarball}" ;;
*.tar) tar --no-same-owner -xf "${tarball}" ;;
*.zip) unzip "${tarball}" ;;
*)
log_err "untar unknown archive format for ${tarball}"

View File

@@ -0,0 +1,107 @@
//go:generate go build
/*
extract generates wrappers of stdlib package exported symbols. This command
is reserved for internal use in yaegi project.
For a similar purpose with third party packages, see the yaegi extract subcommand,
based on the same code.
Output files are written in the current directory, and prefixed with the go version.
Usage:
extract package...
The same program is used for all target operating systems and architectures.
The GOOS and GOARCH environment variables set the desired target.
*/
package main
import (
"bytes"
"flag"
"io"
"log"
"os"
"path"
"runtime"
"strings"
"github.com/traefik/yaegi/extract"
)
var (
exclude = flag.String("exclude", "", "comma separated list of regexp matching symbols to exclude")
include = flag.String("include", "", "comma separated list of regexp matching symbols to include")
)
func main() {
flag.Parse()
if flag.NArg() == 0 {
flag.Usage()
log.Fatalf("missing package path")
}
wd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
ext := extract.Extractor{
Dest: path.Base(wd),
}
goos, goarch := os.Getenv("GOOS"), os.Getenv("GOARCH")
if *exclude != "" {
ext.Exclude = strings.Split(*exclude, ",")
}
if *include != "" {
ext.Include = strings.Split(*include, ",")
}
for _, pkgIdent := range flag.Args() {
var buf bytes.Buffer
if pkgIdent == "syscall" && goos == "solaris" {
// Syscall6 is broken on solaris (https://github.com/golang/go/issues/24357),
// it breaks build, skip related symbols.
ext.Exclude = append(ext.Exclude, "Syscall6")
}
importPath, err := ext.Extract(pkgIdent, "", &buf)
if err != nil {
log.Fatal(err)
}
var oFile string
if pkgIdent == "syscall" {
oFile = strings.ReplaceAll(importPath, "/", "_") + "_" + goos + "_" + goarch + ".go"
} else {
oFile = strings.ReplaceAll(importPath, "/", "_") + ".go"
}
prefix := runtime.Version()
if runtime.Version() != "devel" {
parts := strings.Split(runtime.Version(), ".")
prefix = parts[0] + "_" + extract.GetMinor(parts[1])
}
f, err := os.Create(prefix + "_" + oFile)
if err != nil {
log.Fatal(err)
}
if _, err := io.Copy(f, &buf); err != nil {
_ = f.Close()
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
}
}

View File

@@ -196,6 +196,18 @@ func {{$name}}Const(n *node) {
{{- if $op.Shift}}
v := constant.Shift(vConstantValue(v0), token.{{tokenFromName $name}}, uint(vUint(v1)))
n.rval.Set(reflect.ValueOf(v))
{{- else if (eq $op.Name "/")}}
var operator token.Token
// When the result of the operation is expected to be an int (because both
// operands are ints), we want to force the type of the whole expression to be an
// int (and not a float), which is achieved by using the QUO_ASSIGN operator.
if n.typ.untyped && isInt(n.typ.rtype) {
operator = token.QUO_ASSIGN
} else {
operator = token.QUO
}
v := constant.BinaryOp(vConstantValue(v0), operator, vConstantValue(v1))
n.rval.Set(reflect.ValueOf(v))
{{- else}}
v := constant.BinaryOp(vConstantValue(v0), token.{{tokenFromName $name}}, vConstantValue(v1))
n.rval.Set(reflect.ValueOf(v))
@@ -395,10 +407,10 @@ func {{$name}}Const(n *node) {
case isConst:
v := constant.UnaryOp(token.{{tokenFromName $name}}, vConstantValue(v0), 0)
n.rval.Set(reflect.ValueOf(v))
case isInt(t):
n.rval.SetInt({{$op.Name}} v0.Int())
case isUint(t):
n.rval.SetUint({{$op.Name}} v0.Uint())
case isInt(t):
n.rval.SetInt({{$op.Name}} v0.Int())
{{- if $op.Float}}
case isFloat(t):
n.rval.SetFloat({{$op.Name}} v0.Float())
@@ -931,7 +943,7 @@ type Op struct {
}
func main() {
base := template.New("goexports")
base := template.New("genop")
base.Funcs(template.FuncMap{
"tokenFromName": func(name string) string {
switch name {

View File

@@ -443,10 +443,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.gen = nop
break
}
if n.anc.kind == commClause {
n.gen = nop
break
}
var atyp *itype
if n.nleft+n.nright < len(n.child) {
if atyp, err = nodeType(interp, sc, n.child[n.nleft]); err != nil {
@@ -554,6 +551,14 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.gen = nop
src.findex = dest.findex
src.level = level
case n.action == aAssign && len(n.child) < 4 && !src.rval.IsValid() && isArithmeticAction(src):
// Optimize single assignments from some arithmetic operations.
// Skip the assign operation entirely, the source frame index is set
// to destination index, avoiding extra memory alloc and duplication.
src.typ = dest.typ
src.findex = dest.findex
src.level = level
n.gen = nop
case src.kind == basicLit && !src.rval.IsValid():
// Assign to nil.
src.rval = reflect.New(dest.typ.TypeOf()).Elem()
@@ -855,7 +860,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.gen = nop
n.findex = -1
n.typ = c0.typ
n.rval = c1.rval
n.rval = c1.rval.Convert(c0.typ.rtype)
default:
n.gen = convert
n.typ = c0.typ
@@ -982,7 +987,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.findex = sc.add(n.typ)
// TODO: Check that composite literal expr matches corresponding type
n.gen = compositeGenerator(n, n.typ)
n.gen = compositeGenerator(n, n.typ, nil)
case fallthroughtStmt:
if n.anc.kind != caseBody {
@@ -1129,7 +1134,6 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
sym, level, found := sc.lookup(n.ident)
if !found {
// retry with the filename, in case ident is a package name.
// TODO(mpl): maybe we improve lookup itself so it can deal with that.
sym, level, found = sc.lookup(filepath.Join(n.ident, baseName))
if !found {
err = n.cfgErrorf("undefined: %s", n.ident)
@@ -1302,7 +1306,12 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
}
case returnStmt:
if mustReturnValue(sc.def.child[2]) {
if len(n.child) > sc.def.typ.numOut() {
err = n.cfgErrorf("too many arguments to return")
break
}
returnSig := sc.def.child[2]
if mustReturnValue(returnSig) {
nret := len(n.child)
if nret == 1 && isCall(n.child[0]) {
nret = n.child[0].child[0].typ.numOut()
@@ -1316,13 +1325,19 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.tnext = nil
n.val = sc.def
for i, c := range n.child {
var typ *itype
typ, err = nodeType(interp, sc, returnSig.child[1].fieldType(i))
if err != nil {
return
}
// TODO(mpl): move any of that code to typecheck?
c.typ.node = c
if !c.typ.assignableTo(typ) {
err = fmt.Errorf("cannot use %v (type %v) as type %v in return argument", c.ident, c.typ.cat, typ.cat)
return
}
if c.typ.cat == nilT {
// nil: Set node value to zero of return type
f := sc.def
var typ *itype
if typ, err = nodeType(interp, sc, f.child[2].child[1].fieldType(i)); err != nil {
return
}
if typ.cat == funcT {
// Wrap the typed nil value in a node, as per other interpreter functions
c.rval = reflect.ValueOf(&node{kind: basicLit, rval: reflect.New(typ.TypeOf()).Elem()})
@@ -2360,10 +2375,10 @@ func gotoLabel(s *symbol) {
}
}
func compositeGenerator(n *node, typ *itype) (gen bltnGenerator) {
func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerator) {
switch typ.cat {
case aliasT, ptrT:
gen = compositeGenerator(n, n.typ.val)
gen = compositeGenerator(n, n.typ.val, rtyp)
case arrayT:
gen = arrayLit
case mapT:
@@ -2386,11 +2401,21 @@ func compositeGenerator(n *node, typ *itype) (gen bltnGenerator) {
}
}
case valueT:
switch k := n.typ.rtype.Kind(); k {
if rtyp == nil {
rtyp = n.typ.rtype
}
switch k := rtyp.Kind(); k {
case reflect.Struct:
gen = compositeBinStruct
if n.nleft == 1 {
gen = compositeBinStruct
} else {
gen = compositeBinStructNotype
}
case reflect.Map:
// TODO(mpl): maybe needs a NoType version too
gen = compositeBinMap
case reflect.Ptr:
gen = compositeGenerator(n, typ, n.typ.val.rtype)
default:
log.Panic(n.cfgErrorf("compositeGenerator not implemented for type kind: %s", k))
}
@@ -2432,3 +2457,13 @@ func isValueUntyped(v reflect.Value) bool {
}
return t.String() == t.Kind().String()
}
// isArithmeticAction returns true if the node action is an arithmetic operator.
func isArithmeticAction(n *node) bool {
switch n.action {
case aAdd, aAnd, aAndNot, aBitNot, aMul, aQuo, aRem, aShl, aShr, aSub, aXor:
return true
default:
return false
}
}

View File

@@ -13,8 +13,8 @@ found in vendor, sources modules will be searched in GOPATH. Go modules
are not supported yet by yaegi.
Binary form packages are compiled and linked with the interpreter
executable, and exposed to scripts with the Use method. The goexports
command can be used to generate package wrappers.
executable, and exposed to scripts with the Use method. The extract
subcommand of yaegi can be used to generate package wrappers.
Custom build tags

View File

@@ -248,6 +248,8 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
typeName := n.child[0].ident
var typ *itype
if typ, err = nodeType(interp, sc, n.child[1]); err != nil {
err = nil
revisit = append(revisit, n)
return false
}

View File

@@ -2,8 +2,6 @@ package interp
import "reflect"
const hooksPath = "github.com/traefik/yaegi"
// convertFn is the signature of a symbol converter.
type convertFn func(from, to reflect.Type) func(src, dest reflect.Value)

View File

@@ -155,8 +155,9 @@ type Interpreter struct {
}
const (
mainID = "main"
selfPath = "github.com/traefik/yaegi/interp"
mainID = "main"
selfPrefix = "github.com/traefik/yaegi"
selfPath = selfPrefix + "/interp"
// DefaultSourceName is the name used by default when the name of the input
// source file has not been specified for an Eval.
// TODO(mpl): something even more special as a name?
@@ -175,6 +176,7 @@ var Symbols = Exports{
"Interpreter": reflect.ValueOf((*Interpreter)(nil)),
"Options": reflect.ValueOf((*Options)(nil)),
"Panic": reflect.ValueOf((*Panic)(nil)),
},
}
@@ -401,32 +403,59 @@ func (interp *Interpreter) EvalTest(path string) error {
return err
}
// Symbols returns a map of interpreter exported symbol values for the given path.
func (interp *Interpreter) Symbols(path string) map[string]reflect.Value {
m := map[string]reflect.Value{}
// Symbols returns a map of interpreter exported symbol values for the given
// import path. If the argument is the empty string, all known symbols are
// returned.
func (interp *Interpreter) Symbols(importPath string) Exports {
m := map[string]map[string]reflect.Value{}
interp.mutex.RLock()
if interp.scopes[path] == nil {
interp.mutex.RUnlock()
return m
}
sym := interp.scopes[path].sym
interp.mutex.RUnlock()
defer interp.mutex.RUnlock()
for n, s := range sym {
if !canExport(n) {
// Skip private non-exported symbols.
for k, v := range interp.srcPkg {
if importPath != "" && k != importPath {
continue
}
switch s.kind {
case constSym:
m[n] = s.rval
case funcSym:
m[n] = genFunctionWrapper(s.node)(interp.frame)
case varSym:
m[n] = interp.frame.data[s.index]
syms := map[string]reflect.Value{}
for n, s := range v {
if !canExport(n) {
// Skip private non-exported symbols.
continue
}
switch s.kind {
case constSym:
syms[n] = s.rval
case funcSym:
syms[n] = genFunctionWrapper(s.node)(interp.frame)
case varSym:
syms[n] = interp.frame.data[s.index]
case typeSym:
syms[n] = reflect.New(s.typ.TypeOf())
}
}
if len(syms) > 0 {
m[k] = syms
}
if importPath != "" {
return m
}
}
if importPath != "" && len(m) > 0 {
return m
}
for k, v := range interp.binPkg {
if importPath != "" && k != importPath {
continue
}
m[k] = v
if importPath != "" {
return m
}
}
return m
}
@@ -603,7 +632,7 @@ func (interp *Interpreter) getWrapper(t reflect.Type) reflect.Type {
// they can be used in interpreted code.
func (interp *Interpreter) Use(values Exports) {
for k, v := range values {
if k == hooksPath {
if k == selfPrefix {
interp.hooks.Parse(v)
continue
}
@@ -785,20 +814,28 @@ func (interp *Interpreter) REPL() (reflect.Value, error) {
}
}
func doPrompt(out io.Writer) func(v reflect.Value) {
return func(v reflect.Value) {
if v.IsValid() {
fmt.Fprintln(out, ":", v)
}
fmt.Fprint(out, "> ")
}
}
// getPrompt returns a function which prints a prompt only if input is a terminal.
func getPrompt(in io.Reader, out io.Writer) func(reflect.Value) {
forcePrompt, _ := strconv.ParseBool(os.Getenv("YAEGI_PROMPT"))
if forcePrompt {
return doPrompt(out)
}
s, ok := in.(interface{ Stat() (os.FileInfo, error) })
if !ok {
return func(reflect.Value) {}
}
stat, err := s.Stat()
if err == nil && stat.Mode()&os.ModeCharDevice != 0 {
return func(v reflect.Value) {
if v.IsValid() {
fmt.Fprintln(out, ":", v)
}
fmt.Fprint(out, "> ")
}
return doPrompt(out)
}
return func(reflect.Value) {}
}

View File

@@ -43,6 +43,9 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "for7.go" || // expect error
file.Name() == "fun21.go" || // expect error
file.Name() == "fun22.go" || // expect error
file.Name() == "fun23.go" || // expect error
file.Name() == "fun24.go" || // expect error
file.Name() == "fun25.go" || // expect error
file.Name() == "if2.go" || // expect error
file.Name() == "import6.go" || // expect error
file.Name() == "init1.go" || // expect error
@@ -201,6 +204,11 @@ func TestInterpErrorConsistency(t *testing.T) {
expectedInterp: "6:2: not enough arguments in call to time.Date",
expectedExec: "6:11: not enough arguments in call to time.Date",
},
{
fileName: "fun23.go",
expectedInterp: "3:17: too many arguments to return",
expectedExec: "3:17: too many arguments to return",
},
{
fileName: "op1.go",
expectedInterp: "5:2: invalid operation: mismatched types int and float64",

View File

@@ -956,6 +956,9 @@ func TestConcurrentEvals(t *testing.T) {
hello1 = true
case "hello world2":
hello2 = true
case "hello world1hello world2", "hello world2hello world1":
hello1 = true
hello2 = true
default:
c <- fmt.Errorf("unexpected output: %v", l)
return
@@ -983,6 +986,9 @@ func TestConcurrentEvals(t *testing.T) {
// called by EvalWithContext is sequential. And that there is no data race for the
// interp package global vars or the interpreter fields in this case.
func TestConcurrentEvals2(t *testing.T) {
if testing.Short() {
return
}
pin, pout := io.Pipe()
defer func() {
_ = pin.Close()
@@ -1042,6 +1048,9 @@ func TestConcurrentEvals2(t *testing.T) {
// - when calling Interpreter.Use, the symbols given as argument should be
// copied when being inserted into interp.binPkg, and not directly used as-is.
func TestConcurrentEvals3(t *testing.T) {
if testing.Short() {
return
}
allDone := make(chan bool)
runREPL := func() {
done := make(chan error)
@@ -1120,6 +1129,9 @@ func TestConcurrentComposite2(t *testing.T) {
}
func testConcurrentComposite(t *testing.T, filePath string) {
if testing.Short() {
return
}
pin, pout := io.Pipe()
i := interp.New(interp.Options{Stdout: pout})
i.Use(stdlib.Symbols)
@@ -1157,6 +1169,9 @@ func testConcurrentComposite(t *testing.T, filePath string) {
}
func TestEvalScanner(t *testing.T) {
if testing.Short() {
return
}
type testCase struct {
desc string
src []string
@@ -1328,3 +1343,94 @@ func applyCIMultiplier(timeout time.Duration) time.Duration {
}
return time.Duration(float64(timeout) * CITimeoutMultiplier)
}
func TestREPLDivision(t *testing.T) {
if testing.Short() {
return
}
_ = os.Setenv("YAEGI_PROMPT", "1")
defer func() {
_ = os.Setenv("YAEGI_PROMPT", "0")
}()
allDone := make(chan bool)
runREPL := func() {
done := make(chan error)
pinin, poutin := io.Pipe()
pinout, poutout := io.Pipe()
i := interp.New(interp.Options{Stdin: pinin, Stdout: poutout})
i.Use(stdlib.Symbols)
go func() {
_, _ = i.REPL()
}()
defer func() {
_ = pinin.Close()
_ = poutin.Close()
_ = pinout.Close()
_ = poutout.Close()
allDone <- true
}()
input := []string{
`1/1`,
`7/3`,
`16/5`,
`3./2`, // float
}
output := []string{
`1`,
`2`,
`3`,
`1.5`,
}
go func() {
sc := bufio.NewScanner(pinout)
k := 0
for sc.Scan() {
l := sc.Text()
if l != "> : "+output[k] {
done <- fmt.Errorf("unexpected output, want %q, got %q", output[k], l)
return
}
k++
if k > 3 {
break
}
}
done <- nil
}()
for _, v := range input {
in := strings.NewReader(v + "\n")
if _, err := io.Copy(poutin, in); err != nil {
t.Fatal(err)
}
select {
case err := <-done:
if err != nil {
t.Fatal(err)
}
return
default:
time.Sleep(time.Second)
}
}
if err := <-done; err != nil {
t.Fatal(err)
}
}
go func() {
runREPL()
}()
timeout := time.NewTimer(10 * time.Second)
select {
case <-allDone:
case <-timeout.C:
t.Fatal("timeout")
}
}

View File

@@ -701,7 +701,16 @@ func quoConst(n *node) {
n.rval = reflect.New(t).Elem()
switch {
case isConst:
v := constant.BinaryOp(vConstantValue(v0), token.QUO, vConstantValue(v1))
var operator token.Token
// When the result of the operation is expected to be an int (because both
// operands are ints), we want to force the type of the whole expression to be an
// int (and not a float), which is achieved by using the QUO_ASSIGN operator.
if n.typ.untyped && isInt(n.typ.rtype) {
operator = token.QUO_ASSIGN
} else {
operator = token.QUO
}
v := constant.BinaryOp(vConstantValue(v0), operator, vConstantValue(v1))
n.rval.Set(reflect.ValueOf(v))
case isComplex(t):
n.rval.SetComplex(vComplex(v0) / vComplex(v1))
@@ -1957,10 +1966,10 @@ func bitNotConst(n *node) {
case isConst:
v := constant.UnaryOp(token.XOR, vConstantValue(v0), 0)
n.rval.Set(reflect.ValueOf(v))
case isInt(t):
n.rval.SetInt(^v0.Int())
case isUint(t):
n.rval.SetUint(^v0.Uint())
case isInt(t):
n.rval.SetInt(^v0.Int())
}
}
@@ -1976,10 +1985,10 @@ func negConst(n *node) {
case isConst:
v := constant.UnaryOp(token.SUB, vConstantValue(v0), 0)
n.rval.Set(reflect.ValueOf(v))
case isInt(t):
n.rval.SetInt(-v0.Int())
case isUint(t):
n.rval.SetUint(-v0.Uint())
case isInt(t):
n.rval.SetInt(-v0.Int())
case isFloat(t):
n.rval.SetFloat(-v0.Float())
case isComplex(t):
@@ -2015,10 +2024,10 @@ func posConst(n *node) {
case isConst:
v := constant.UnaryOp(token.ADD, vConstantValue(v0), 0)
n.rval.Set(reflect.ValueOf(v))
case isInt(t):
n.rval.SetInt(+v0.Int())
case isUint(t):
n.rval.SetUint(+v0.Uint())
case isInt(t):
n.rval.SetInt(+v0.Int())
case isFloat(t):
n.rval.SetFloat(+v0.Float())
case isComplex(t):

View File

@@ -1,6 +1,6 @@
package interp
//go:generate go run ../internal/genop/genop.go
//go:generate go run ../internal/cmd/genop/genop.go
import (
"fmt"
@@ -1869,7 +1869,10 @@ func _return(n *node) {
case 0:
n.exec = nil
case 1:
if child[0].kind == binaryExpr || isCall(child[0]) {
// This is an optimisation that is applied for binary expressions or function
// calls, but not for (binary) expressions involving const, as the values are not
// stored in the frame in that case.
if !child[0].rval.IsValid() && child[0].kind == binaryExpr || isCall(child[0]) {
n.exec = nil
} else {
v := values[0]
@@ -2010,12 +2013,18 @@ func compositeBinMap(n *node) {
}
}
// compositeBinStruct creates and populates a struct object from a binary type.
func compositeBinStruct(n *node) {
// doCompositeBinStruct creates and populates a struct object from a binary type.
func doCompositeBinStruct(n *node, hasType bool) {
next := getExec(n.tnext)
value := valueGenerator(n, n.findex)
typ := n.typ.rtype
child := n.child[1:]
if n.typ.cat == ptrT || n.typ.cat == aliasT {
typ = n.typ.val.rtype
}
child := n.child
if hasType {
child = n.child[1:]
}
values := make([]func(*frame) reflect.Value, len(child))
fieldIndex := make([][]int, len(child))
for i, c := range child {
@@ -2031,7 +2040,7 @@ func compositeBinStruct(n *node) {
}
} else {
fieldIndex[i] = []int{i}
if c.typ.cat == funcT {
if c.typ.cat == funcT && len(c.child) > 1 {
convertLiteralValue(c.child[1], typ.Field(i).Type)
values[i] = genFunctionWrapper(c.child[1])
} else {
@@ -2046,11 +2055,20 @@ func compositeBinStruct(n *node) {
for i, v := range values {
s.FieldByIndex(fieldIndex[i]).Set(v(f))
}
value(f).Set(s)
d := value(f)
switch {
case d.Type().Kind() == reflect.Ptr:
d.Set(s.Addr())
default:
d.Set(s)
}
return next
}
}
func compositeBinStruct(n *node) { doCompositeBinStruct(n, true) }
func compositeBinStructNotype(n *node) { doCompositeBinStruct(n, false) }
func destType(n *node) *itype {
switch n.anc.kind {
case assignStmt, defineStmt:
@@ -2412,11 +2430,16 @@ func appendSlice(n *node) {
}
func _append(n *node) {
if c1, c2 := n.child[1], n.child[2]; len(n.child) == 3 && c2.typ.cat == arrayT && c2.typ.val.id() == n.typ.val.id() ||
isByteArray(c1.typ.TypeOf()) && isString(c2.typ.TypeOf()) {
appendSlice(n)
return
if len(n.child) == 3 {
c1, c2 := n.child[1], n.child[2]
if (c1.typ.cat == valueT || c2.typ.cat == valueT) && c1.typ.rtype == c2.typ.rtype ||
c2.typ.cat == arrayT && c2.typ.val.id() == n.typ.val.id() ||
isByteArray(c1.typ.TypeOf()) && isString(c2.typ.TypeOf()) {
appendSlice(n)
return
}
}
dest := genValueOutput(n, n.typ.rtype)
value := genValue(n.child[1])
next := getExec(n.tnext)
@@ -2813,65 +2836,29 @@ func convertConstantValue(n *node) {
return
}
v := n.rval
typ := n.typ.TypeOf()
kind := typ.Kind()
switch kind {
case reflect.Bool:
v = reflect.ValueOf(constant.BoolVal(c)).Convert(typ)
case reflect.String:
v = reflect.ValueOf(constant.StringVal(c)).Convert(typ)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i, _ := constant.Int64Val(c)
l := constant.BitLen(c)
if l > bitlen[kind] {
panic(fmt.Sprintf("constant %s overflows int%d", c.ExactString(), bitlen[kind]))
var v reflect.Value
switch c.Kind() {
case constant.Bool:
v = reflect.ValueOf(constant.BoolVal(c))
case constant.String:
v = reflect.ValueOf(constant.StringVal(c))
case constant.Int:
i, x := constant.Int64Val(c)
if !x {
panic(fmt.Sprintf("constant %s overflows int64", c.ExactString()))
}
v = reflect.ValueOf(i).Convert(typ)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
i, _ := constant.Uint64Val(c)
l := constant.BitLen(c)
if l > bitlen[kind] {
panic(fmt.Sprintf("constant %s overflows uint%d", c.ExactString(), bitlen[kind]))
}
v = reflect.ValueOf(i).Convert(typ)
case reflect.Float32:
f, _ := constant.Float32Val(c)
v = reflect.ValueOf(f).Convert(typ)
case reflect.Float64:
v = reflect.ValueOf(int(i))
case constant.Float:
f, _ := constant.Float64Val(c)
v = reflect.ValueOf(f).Convert(typ)
case reflect.Complex64:
r, _ := constant.Float32Val(constant.Real(c))
i, _ := constant.Float32Val(constant.Imag(c))
v = reflect.ValueOf(complex(r, i)).Convert(typ)
case reflect.Complex128:
v = reflect.ValueOf(f)
case constant.Complex:
r, _ := constant.Float64Val(constant.Real(c))
i, _ := constant.Float64Val(constant.Imag(c))
v = reflect.ValueOf(complex(r, i)).Convert(typ)
default:
// Type kind is from internal constant representation. Only use default types here.
switch c.Kind() {
case constant.Bool:
v = reflect.ValueOf(constant.BoolVal(c))
case constant.String:
v = reflect.ValueOf(constant.StringVal(c))
case constant.Int:
i, x := constant.Int64Val(c)
if !x {
panic(fmt.Sprintf("constant %s overflows int64", c.ExactString()))
}
v = reflect.ValueOf(int(i))
case constant.Float:
f, _ := constant.Float64Val(c)
v = reflect.ValueOf(f)
case constant.Complex:
r, _ := constant.Float64Val(constant.Real(c))
i, _ := constant.Float64Val(constant.Imag(c))
v = reflect.ValueOf(complex(r, i))
}
v = reflect.ValueOf(complex(r, i))
}
n.rval = v
n.rval = v.Convert(n.typ.TypeOf())
}
// Write to a channel.
@@ -2951,37 +2938,40 @@ func _select(n *node) {
next := getExec(n.tnext)
for i := 0; i < nbClause; i++ {
if len(n.child[i].child) == 0 {
// The comm clause is an empty default, exit select.
cl := n.child[i]
if cl.kind == commClauseDefault {
cases[i].Dir = reflect.SelectDefault
clause[i] = func(*frame) bltn { return next }
} else {
switch c0 := n.child[i].child[0]; {
case len(n.child[i].child) > 1:
// The comm clause contains a channel operation and a clause body.
clause[i] = getExec(n.child[i].child[1].start)
chans[i], assigned[i], ok[i], cases[i].Dir = clauseChanDir(n.child[i])
chanValues[i] = genValue(chans[i])
if assigned[i] != nil {
assignedValues[i] = genValue(assigned[i])
}
if ok[i] != nil {
okValues[i] = genValue(ok[i])
}
case c0.kind == exprStmt && len(c0.child) == 1 && c0.child[0].action == aRecv:
// The comm clause has an empty body clause after channel receive.
chanValues[i] = genValue(c0.child[0].child[0])
cases[i].Dir = reflect.SelectRecv
case c0.kind == sendStmt:
// The comm clause as an empty body clause after channel send.
chanValues[i] = genValue(c0.child[0])
cases[i].Dir = reflect.SelectSend
assignedValues[i] = genValue(c0.child[1])
default:
// The comm clause has a default clause.
clause[i] = getExec(c0.start)
cases[i].Dir = reflect.SelectDefault
if len(cl.child) == 0 {
clause[i] = func(*frame) bltn { return next }
} else {
clause[i] = getExec(cl.child[0].start)
}
continue
}
// The comm clause is in send or recv direction.
switch c0 := cl.child[0]; {
case len(cl.child) > 1:
// The comm clause contains a channel operation and a clause body.
clause[i] = getExec(cl.child[1].start)
chans[i], assigned[i], ok[i], cases[i].Dir = clauseChanDir(c0)
chanValues[i] = genValue(chans[i])
if assigned[i] != nil {
assignedValues[i] = genValue(assigned[i])
}
if ok[i] != nil {
okValues[i] = genValue(ok[i])
}
case c0.kind == exprStmt && len(c0.child) == 1 && c0.child[0].action == aRecv:
// The comm clause has an empty body clause after channel receive.
chanValues[i] = genValue(c0.child[0].child[0])
cases[i].Dir = reflect.SelectRecv
clause[i] = func(*frame) bltn { return next }
case c0.kind == sendStmt:
// The comm clause as an empty body clause after channel send.
chanValues[i] = genValue(c0.child[0])
cases[i].Dir = reflect.SelectSend
assignedValues[i] = genValue(c0.child[1])
clause[i] = func(*frame) bltn { return next }
}
}

View File

@@ -33,17 +33,12 @@ func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (s
rPath = "."
}
dir = filepath.Join(filepath.Dir(interp.name), rPath, importPath)
} else {
var root string
if rPath == mainID {
root, err = interp.rootFromSourceLocation()
if err != nil {
return "", err
}
} else {
root = rPath
} else if dir, rPath, err = pkgDir(interp.context.GOPATH, rPath, importPath); err != nil {
// Try again, assuming a root dir at the source location.
if rPath, err = interp.rootFromSourceLocation(); err != nil {
return "", err
}
if dir, rPath, err = pkgDir(interp.context.GOPATH, root, importPath); err != nil {
if dir, rPath, err = pkgDir(interp.context.GOPATH, rPath, importPath); err != nil {
return "", err
}
}

View File

@@ -166,56 +166,68 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
case arrayType:
t.cat = arrayT
if len(n.child) > 1 {
v := n.child[0].rval
switch {
case v.IsValid():
// constant size
if isConstantValue(v.Type()) {
c := v.Interface().(constant.Value)
t.size = constToInt(c)
} else {
t.size = int(v.Int())
}
case n.child[0].kind == ellipsisExpr:
// [...]T expression
t.size = arrayTypeLen(n.anc)
default:
if sym, _, ok := sc.lookup(n.child[0].ident); ok {
if sym.kind != constSym {
return nil, n.child[0].cfgErrorf("non-constant array bound %q", n.child[0].ident)
}
// Resolve symbol to get size value
if sym.typ != nil && sym.typ.cat == intT {
if v, ok := sym.rval.Interface().(int); ok {
t.size = v
} else if c, ok := sym.rval.Interface().(constant.Value); ok {
t.size = constToInt(c)
} else {
t.incomplete = true
}
} else {
t.incomplete = true
}
} else {
// Evaluate constant array size expression
if _, err = interp.cfg(n.child[0], sc.pkgID); err != nil {
return nil, err
}
t.incomplete = true
}
}
if t.val, err = nodeType(interp, sc, n.child[1]); err != nil {
return nil, err
}
t.sizedef = true
t.incomplete = t.incomplete || t.val.incomplete
} else {
if t.val, err = nodeType(interp, sc, n.child[0]); err != nil {
c0 := n.child[0]
if len(n.child) == 1 {
// Array size is not defined.
if t.val, err = nodeType(interp, sc, c0); err != nil {
return nil, err
}
t.incomplete = t.val.incomplete
break
}
// Array size is defined.
switch v := c0.rval; {
case v.IsValid():
// Size if defined by a constant litteral value.
if isConstantValue(v.Type()) {
c := v.Interface().(constant.Value)
t.size = constToInt(c)
} else {
t.size = int(v.Int())
}
case c0.kind == ellipsisExpr:
// [...]T expression, get size from the length of composite array.
t.size = arrayTypeLen(n.anc)
case c0.kind == identExpr:
sym, _, ok := sc.lookup(c0.ident)
if !ok {
t.incomplete = true
break
}
// Size is defined by a symbol which must be a constant integer.
if sym.kind != constSym {
return nil, c0.cfgErrorf("non-constant array bound %q", c0.ident)
}
if sym.typ == nil || sym.typ.cat != intT {
t.incomplete = true
break
}
if v, ok := sym.rval.Interface().(int); ok {
t.size = v
break
}
if c, ok := sym.rval.Interface().(constant.Value); ok {
t.size = constToInt(c)
break
}
t.incomplete = true
default:
// Size is defined by a numeric constant expression.
if _, err = interp.cfg(c0, sc.pkgID); err != nil {
return nil, err
}
v, ok := c0.rval.Interface().(constant.Value)
if !ok {
t.incomplete = true
break
}
t.size = constToInt(v)
}
if t.val, err = nodeType(interp, sc, n.child[1]); err != nil {
return nil, err
}
t.sizedef = true
t.incomplete = t.incomplete || t.val.incomplete
case basicLit:
switch v := n.rval.Interface().(type) {
@@ -550,7 +562,6 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
}
} else {
err = n.cfgErrorf("undefined selector %s.%s", lt.path, name)
panic(err)
}
case srcPkgT:
pkg := interp.srcPkg[lt.path]
@@ -604,7 +615,7 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
t.field = append(t.field, structField{name: fieldName(c.child[0]), embed: true, typ: typ})
incomplete = incomplete || typ.incomplete
case len(c.child) == 2 && c.child[1].kind == basicLit:
tag := c.child[1].rval.String()
tag := vString(c.child[1].rval)
typ, err := nodeType(interp, sc, c.child[0])
if err != nil {
return nil, err
@@ -615,7 +626,7 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
var tag string
l := len(c.child)
if c.lastChild().kind == basicLit {
tag = c.lastChild().rval.String()
tag = vString(c.lastChild().rval)
l--
}
typ, err := nodeType(interp, sc, c.child[l-1])
@@ -915,7 +926,23 @@ func (t *itype) assignableTo(o *itype) bool {
if t.isNil() && o.hasNil() || o.isNil() && t.hasNil() {
return true
}
return t.TypeOf().AssignableTo(o.TypeOf())
if t.TypeOf().AssignableTo(o.TypeOf()) {
return true
}
n := t.node
if n == nil || !n.rval.IsValid() {
return false
}
con, ok := n.rval.Interface().(constant.Value)
if !ok {
return false
}
if con == nil || !isConstType(o) {
return false
}
return representableConst(con, o.TypeOf())
}
// convertibleTo returns true if t is convertible to o.
@@ -924,7 +951,7 @@ func (t *itype) convertibleTo(o *itype) bool {
return true
}
// unsafe checkes
// unsafe checks
tt, ot := t.TypeOf(), o.TypeOf()
if (tt.Kind() == reflect.Ptr || tt.Kind() == reflect.Uintptr) && ot.Kind() == reflect.UnsafePointer {
return true
@@ -1180,7 +1207,11 @@ func (t *itype) lookupBinField(name string) (s reflect.StructField, index []int,
if !isStruct(t) {
return
}
s, ok = t.TypeOf().FieldByName(name)
rt := t.rtype
if t.cat == valueT && rt.Kind() == reflect.Ptr {
rt = rt.Elem()
}
s, ok = rt.FieldByName(name)
if !ok {
for i, f := range t.field {
if f.embed {

View File

@@ -91,7 +91,10 @@ func (check typecheck) addressExpr(n *node) error {
case selectorExpr:
c0 = c0.child[1]
continue
case indexExpr:
case starExpr:
c0 = c0.child[0]
continue
case indexExpr, sliceExpr:
c := c0.child[0]
if isArray(c.typ) || isMap(c.typ) {
c0 = c
@@ -101,7 +104,7 @@ func (check typecheck) addressExpr(n *node) error {
found = true
continue
}
return n.cfgErrorf("invalid operation: cannot take address of %s", c0.typ.id())
return n.cfgErrorf("invalid operation: cannot take address of %s [kind: %s]", c0.typ.id(), kinds[c0.kind])
}
return nil
}
@@ -148,7 +151,7 @@ func (check typecheck) shift(n *node) error {
t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf()
var v0 constant.Value
if c0.typ.untyped {
if c0.typ.untyped && c0.rval.IsValid() {
v0 = constant.ToInt(c0.rval.Interface().(constant.Value))
c0.rval = reflect.ValueOf(v0)
}
@@ -483,7 +486,7 @@ func (check typecheck) sliceExpr(n *node) error {
case reflect.Array:
valid = true
l = t.Len()
if c.kind != selectorExpr && (c.sym == nil || c.sym.kind != varSym) {
if c.kind != indexExpr && c.kind != selectorExpr && (c.sym == nil || c.sym.kind != varSym) {
return c.cfgErrorf("cannot slice type %s", c.typ.id())
}
case reflect.Slice:

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract archive/tar'. DO NOT EDIT.
// Code generated by 'yaegi extract archive/tar'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract archive/zip'. DO NOT EDIT.
// Code generated by 'yaegi extract archive/zip'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract bufio'. DO NOT EDIT.
// Code generated by 'yaegi extract bufio'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract bytes'. DO NOT EDIT.
// Code generated by 'yaegi extract bytes'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract compress/bzip2'. DO NOT EDIT.
// Code generated by 'yaegi extract compress/bzip2'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract compress/flate'. DO NOT EDIT.
// Code generated by 'yaegi extract compress/flate'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract compress/gzip'. DO NOT EDIT.
// Code generated by 'yaegi extract compress/gzip'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract compress/lzw'. DO NOT EDIT.
// Code generated by 'yaegi extract compress/lzw'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract compress/zlib'. DO NOT EDIT.
// Code generated by 'yaegi extract compress/zlib'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract container/heap'. DO NOT EDIT.
// Code generated by 'yaegi extract container/heap'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract container/list'. DO NOT EDIT.
// Code generated by 'yaegi extract container/list'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract container/ring'. DO NOT EDIT.
// Code generated by 'yaegi extract container/ring'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract context'. DO NOT EDIT.
// Code generated by 'yaegi extract context'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto/aes'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto/aes'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto/cipher'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto/cipher'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto/des'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto/des'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto/dsa'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto/dsa'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto/ecdsa'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto/ecdsa'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto/ed25519'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto/ed25519'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto/elliptic'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto/elliptic'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto/hmac'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto/hmac'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto/md5'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto/md5'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto/rand'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto/rand'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto/rc4'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto/rc4'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto/rsa'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto/rsa'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto/sha1'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto/sha1'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto/sha256'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto/sha256'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto/sha512'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto/sha512'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto/subtle'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto/subtle'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto/tls'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto/tls'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto/x509'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto/x509'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract crypto/x509/pkix'. DO NOT EDIT.
// Code generated by 'yaegi extract crypto/x509/pkix'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract database/sql'. DO NOT EDIT.
// Code generated by 'yaegi extract database/sql'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract database/sql/driver'. DO NOT EDIT.
// Code generated by 'yaegi extract database/sql/driver'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract debug/dwarf'. DO NOT EDIT.
// Code generated by 'yaegi extract debug/dwarf'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract debug/elf'. DO NOT EDIT.
// Code generated by 'yaegi extract debug/elf'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract debug/gosym'. DO NOT EDIT.
// Code generated by 'yaegi extract debug/gosym'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract debug/macho'. DO NOT EDIT.
// Code generated by 'yaegi extract debug/macho'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract debug/pe'. DO NOT EDIT.
// Code generated by 'yaegi extract debug/pe'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract debug/plan9obj'. DO NOT EDIT.
// Code generated by 'yaegi extract debug/plan9obj'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract encoding'. DO NOT EDIT.
// Code generated by 'yaegi extract encoding'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract encoding/ascii85'. DO NOT EDIT.
// Code generated by 'yaegi extract encoding/ascii85'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract encoding/asn1'. DO NOT EDIT.
// Code generated by 'yaegi extract encoding/asn1'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract encoding/base32'. DO NOT EDIT.
// Code generated by 'yaegi extract encoding/base32'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract encoding/base64'. DO NOT EDIT.
// Code generated by 'yaegi extract encoding/base64'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract encoding/binary'. DO NOT EDIT.
// Code generated by 'yaegi extract encoding/binary'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'github.com/traefik/yaegi/extract encoding/csv'. DO NOT EDIT.
// Code generated by 'yaegi extract encoding/csv'. DO NOT EDIT.
// +build go1.14,!go1.15

Some files were not shown because too many files have changed in this diff Show More