Compare commits

...

60 Commits

Author SHA1 Message Date
Marc Vertes
7037424edf fix: correctly store boolean result for branching operations 2020-02-14 16:26:04 +01:00
Marc Vertes
1b971b539c fix: correctly handle arbitrary type of literal array index 2020-02-12 15:06:04 +01:00
Marc Vertes
681f2f9c40 fix: correctly handle constant init for further type declarations 2020-02-12 12:32:03 +01:00
Marc Vertes
05960316f8 fix: correct type inference in composite literal init 2020-02-11 10:10:04 +01:00
Marc Vertes
902af477b8 fix: correct behavior for rune and byte types 2020-02-09 05:18:04 +01:00
Marc Vertes
812e55b95e fix: handle conversion of nil to an interface type 2020-02-09 05:04:04 +01:00
Marc Vertes
6c339ce562 fix: handle method declaration with forward declared type 2020-02-07 15:44:04 +01:00
Marc Vertes
23dfef0ac8 fix: define a correct zero value for an not initialized interface{} 2020-02-04 18:04:05 +01:00
Marc Vertes
4fd6a2dc56 fix: handle recursive type definition involving a map object 2020-02-04 16:36:05 +01:00
Marc Vertes
92a63dbe09 fix: handle out of order type declaration for global var declaration 2020-02-03 17:36:04 +01:00
Marc Vertes
e434892b9a fix: import different source packages with same base name 2020-02-03 17:22:04 +01:00
Marc Vertes
712891dd77 fix: allow reuse of _ symbol in the same scope 2020-02-03 17:08:05 +01:00
Marc Vertes
137b16580c fix: handle binary methods where receiver is implicit 2020-02-03 16:54:04 +01:00
Sven Dowideit
b057ada531 Add an example of yaegi in a shebang line 2020-01-15 16:54:06 +01:00
Camal Cakar
6d90c5a641 Case sensitivity fix 2020-01-15 16:40:06 +01:00
Marc Vertes
5381ee65d1 fix: continue statement was not applied correctly 2020-01-10 17:50:05 +01:00
Marc Vertes
f1cde2be0f fix: apply automatic type conversion to constant expressions (#484) 2020-01-09 18:01:44 +01:00
Marc Vertes
bb04af2d4d doc: document import of source and binary packages (#477) 2020-01-08 18:51:30 +01:00
Marc Vertes
9a8a88dcb9 fix: use branch operation in || and && operators, fix storage for ! (#476) 2020-01-07 17:27:22 +01:00
Marc Vertes
f3f54a5302 doc: add explanation about CLA in CONTRIBUTING.md (#483) 2020-01-07 16:30:05 +01:00
Marc Vertes
878fcc835c fix: add support for ^ and + as unary operators 2020-01-07 15:34:05 +01:00
Marc Vertes
a1f2d3bf1d fix: isNil was not forwarding result when used in a branch expression 2019-12-19 18:32:04 +01:00
Marc Vertes
3cd37645eb fix: correct isValueUntyped() to handle typed constants 2019-12-19 15:38:05 +01:00
Marc Vertes
e1ac83f7d8 fix: correct type extraction for returned value 2019-12-17 10:18:06 +01:00
Marc Vertes
4f93be7f19 fix: emulate struct by interface{} only for recursive struct types 2019-12-16 19:00:07 +01:00
Marc Vertes
7a0c09f5eb fix: detect untyped values when importing from binary packages 2019-12-13 11:18:04 +01:00
Marc Vertes
275391c1e8 fix: struct type detection, collision between field and type name 2019-12-12 14:40:05 +01:00
Marc Vertes
273df8af9f fix: improve interface type checks using method sets 2019-12-11 14:46:06 +01:00
Marc Vertes
0d2c39d155 fix: implicit import package name was not correctly generated 2019-12-11 11:54:05 +01:00
Marc Vertes
1ff1a50753 fix: add method checks for interface types 2019-12-09 18:24:04 +01:00
Marc Vertes
488e491bf8 fix: improve type switch clause with assign 2019-11-27 23:00:04 +01:00
Marc Vertes
eef59153d8 fix: detect non boolean condition for IF and FOR statements 2019-11-26 00:08:05 +01:00
Marc Vertes
d44e4af527 fix: handle selector expression in type switch 2019-11-25 23:52:03 +01:00
Marc Vertes
786ea366ab fix: handle nil function closure 2019-11-25 23:36:03 +01:00
Marc Vertes
e506969172 fix: correct handling of dynamic type for interface values 2019-11-25 23:20:04 +01:00
Marc Vertes
9f1f31210a fix: automatic type conversion when returning untyped value 2019-11-19 15:22:05 +01:00
Marc Vertes
56bec974e1 fix: handle index expression on binary string type 2019-11-19 15:06:05 +01:00
Marc Vertes
08a37fc4bf fix: handle type assertion from literal interface type 2019-11-19 14:50:06 +01:00
Marc Vertes
c5ec5e492f fix: assign a literal composite to an interface object 2019-11-19 14:34:05 +01:00
Marc Vertes
773147ef71 fix: properly align atomic counters 2019-11-08 00:34:04 +01:00
Marc Vertes
a6ecebab92 fix: automatic conversion of untyped literal float to int 2019-10-31 17:32:05 +01:00
Dan Kortschak
d893a7427e doc: fix spelling 2019-10-30 11:20:05 +01:00
Marc Vertes
3969ab16c4 fix: improve handling of untyped complex numbers 2019-10-29 18:14:05 +01:00
Dan Kortschak
714253c1e6 interp: add eval cancelation by semaphore 2019-10-29 16:18:04 +01:00
Marc Vertes
75a696a5c8 fix: goexports performs correct conversion on float32 constants 2019-10-29 10:20:05 +01:00
Marc Vertes
15686873e0 fix: assign binary func to func type var 2019-10-20 04:52:03 +02:00
Marc Vertes
ac504a2e8a fix: assign untyped value to typed var may require type conversion 2019-10-20 02:30:03 +02:00
Ludovic Fernandez
e193d95dc2 feat: update syscall for go1.12.12 2019-10-19 18:30:04 +02:00
Marc Vertes
7164a23664 fix: do not hide receiver type for method with anonymous receiver 2019-10-19 17:44:03 +02:00
Marc Vertes
0b4dcbf7bb feature: add support for custom build tags 2019-10-11 16:02:05 +02:00
Ludovic Fernandez
2765478137 chore: update linter to support go1.13 2019-10-09 14:14:04 +02:00
Marc Vertes
de5a6e1038 feature: rename exported func Repl into REPL 2019-10-08 23:54:04 +02:00
Dan Kortschak
398b0e0255 interp: use io.Reader and io.Writer for REPL parameters 2019-10-08 17:34:05 +02:00
Marc Vertes
4f95c27634 fix: generate closures for literal functions in CFG 2019-10-05 19:26:04 +02:00
Marc Vertes
7d19108f01 fix: compute type of slice expression globally 2019-10-05 19:14:04 +02:00
Ludovic Fernandez
1cf327bd7d Drop go1.11 2019-10-01 14:40:05 +02:00
Dan Kortschak
4bf4aeecbb interp: fix map range handling 2019-10-01 13:54:03 +02:00
Dan Kortschak
47923866ff interp: fix array size assignment type inference 2019-09-30 22:58:04 +02:00
Dan Kortschak
bb2921b42f interp: fix range expression handling 2019-09-30 22:44:04 +02:00
Marc Vertes
2c2b471cb9 fix: goexports to convert value type only for untyped constants 2019-09-27 15:44:05 +02:00
309 changed files with 3414 additions and 77369 deletions

View File

@@ -17,11 +17,11 @@ func main() {
Expected result: Expected result:
```console ```console
$ go run ./sample.go $ go run ./sample.go
// ouput // output
``` ```
Got: Got:
```console ```console
$ yaegi ./sample.go $ yaegi ./sample.go
// ouput // output
``` ```

View File

@@ -5,7 +5,7 @@
[linters-settings] [linters-settings]
[linters-settings.govet] [linters-settings.govet]
check-shadowing = true check-shadowing = false
[linters-settings.gocyclo] [linters-settings.gocyclo]
min-complexity = 12.0 min-complexity = 12.0
@@ -32,7 +32,11 @@
"gocyclo", "gocyclo",
"gochecknoinits", "gochecknoinits",
"gochecknoglobals", "gochecknoglobals",
"typecheck", # v1.17.1 and Go1.13 => bug "wsl",
"godox",
"funlen",
"gocognit",
"stylecheck",
] ]
[issues] [issues]
@@ -41,10 +45,13 @@
max-same-issues = 0 max-same-issues = 0
exclude = [] exclude = []
[[issues.exclude-rules]]
path = "cmd/goexports/goexports.go"
text = "SA1019: importer.For is deprecated: use ForCompiler, which populates a FileSet with the positions of objects created by the importer."
[[issues.exclude-rules]] [[issues.exclude-rules]]
path = "interp/.+_test\\.go" path = "interp/.+_test\\.go"
linters = ["goconst"] linters = ["goconst"]
[[issues.exclude-rules]]
path = "interp/interp.go"
text = "`in` can be `io.Reader`"
[[issues.exclude-rules]]
path = "interp/interp.go"
text = "`out` can be `io.Writer`"

View File

@@ -19,7 +19,6 @@ cache:
matrix: matrix:
fast_finish: true fast_finish: true
include: include:
- go: 1.11.x
- go: 1.12.x - go: 1.12.x
- go: 1.13.x - go: 1.13.x
env: STABLE=true env: STABLE=true

View File

@@ -13,7 +13,12 @@ discussions.
Once the proposal is approved, a Pull Request can be opened. If you want Once the proposal is approved, a Pull Request can be opened. If you want
to provide early visibility to reviewers, create a [Draft Pull Request]. to provide early visibility to reviewers, create a [Draft Pull Request].
We will also require you to sign the [Containous Contributor License Agreement]
after you submit your first pull request to this project. The link to sign the
agreement will be presented to you in the web interface of the pull request.
[Issues]: https://github.com/containous/yaegi/issues [Issues]: https://github.com/containous/yaegi/issues
[Pull Requests]: https://github.com/containous/yaegi/issues [Pull Requests]: https://github.com/containous/yaegi/issues
[Feature Request]: https://github.com/containous/yaegi/issues/new?template=feature_request.md [Feature Request]: https://github.com/containous/yaegi/issues/new?template=feature_request.md
[Draft Pull Request]: https://github.blog/2019-02-14-introducing-draft-pull-requests/ [Draft Pull Request]: https://github.blog/2019-02-14-introducing-draft-pull-requests/
[Containous Contributor License Agreement]: https://cla-assistant.io/containous/yaegi

View File

@@ -18,5 +18,6 @@ generate: gen_all_syscall
tests: tests:
GO111MODULE=off go test -v ./... GO111MODULE=off go test -v ./...
GO111MODULE=off go test -race ./interp
.PHONY: check gen_all_syscall gen_tests .PHONY: check gen_all_syscall gen_tests

View File

@@ -129,6 +129,24 @@ $ yaegi cmd/yaegi/yaegi.go
> >
``` ```
Or for Go scripting in the shebang line:
```console
$ cat /tmp/test
#!/usr/bin/env yaegi
package main
import "fmt"
func main() {
fmt.Println("test")
}
$ ls -la /tmp/test
-rwxr-xr-x 1 dow184 dow184 93 Jan 6 13:38 /tmp/test
$ /tmp/test
test
```
## Documentation ## Documentation
Documentation about Yaegi commands and libraries can be found at usual [godoc.org][docs]. Documentation about Yaegi commands and libraries can be found at usual [godoc.org][docs].

12
_test/a33.go Normal file
View File

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

12
_test/a34.go Normal file
View File

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

13
_test/a35.go Normal file
View File

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

13
_test/a36.go Normal file
View File

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

11
_test/a37.go Normal file
View File

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

11
_test/a38.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import "fmt"
func main() {
a := [...]byte{}
fmt.Printf("%T\n", a)
}
// Output:
// [0]uint8

12
_test/a39.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import "fmt"
func main() {
a := [...]byte{}
b := a
fmt.Printf("%T %T\n", a, b)
}
// Output:
// [0]uint8 [0]uint8

23
_test/a40.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import "fmt"
type rule uint8
const (
r0 rule = iota
r1
r2
)
var a = [...]int{
r0: 1,
r1: 12,
}
func main() {
fmt.Println(a)
}
// Output:
// [1 12]

10
_test/a41.go Normal file
View File

@@ -0,0 +1,10 @@
package main
var a = [...]bool{true, true}
func main() {
println(a[0] && true)
}
// Output:
// true

10
_test/and3.go Normal file
View File

@@ -0,0 +1,10 @@
package main
var a = true && true
func main() {
println(a)
}
// Output:
// true

10
_test/assign10.go Normal file
View File

@@ -0,0 +1,10 @@
package main
func main() {
var a uint
a = 1 + 2
println(a)
}
// Output:
// 3

7
_test/b1/foo/foo.go Normal file
View File

@@ -0,0 +1,7 @@
package foo
import bar "github.com/containous/yaegi/_test/b2/foo"
var Desc = "in b1/foo"
var Desc2 = Desc + bar.Desc

3
_test/b2/foo/foo.go Normal file
View File

@@ -0,0 +1,3 @@
package foo
var Desc = "in b2/foo"

13
_test/bin2.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println(math.Abs(-5))
}
// Output:
// 5

12
_test/chan8.go Normal file
View File

@@ -0,0 +1,12 @@
package main
func main() {
messages := make(chan bool)
go func() { messages <- true }()
println(<-messages && true)
}
// Output:
// true

10
_test/closure8.go Normal file
View File

@@ -0,0 +1,10 @@
package main
var f = func(a int) int { return 2 + a }
func main() {
println(f(3))
}
// Output:
// 5

12
_test/complex1.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import "fmt"
func main() {
var c complex128
c = 1
fmt.Printf("%T %v\n", c, c)
}
// Output:
// complex128 (1+0i)

12
_test/complex2.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import "fmt"
func main() {
c := complex(1, 0)
c += 1
fmt.Printf("%T %v\n", c, c)
}
// Output:
// complex128 (2+0i)

11
_test/complex3.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import "fmt"
func main() {
var s int = 1 + complex(1, 0)
fmt.Printf("%T %v\n", s, s)
}
// Output
// int 2

12
_test/composite3.go Normal file
View File

@@ -0,0 +1,12 @@
package main
func main() {
var err error
var ok bool
_, ok = err.(interface{ IsSet() bool })
println(ok)
}
// Output:
// false

11
_test/composite4.go Normal file
View File

@@ -0,0 +1,11 @@
package main
func main() {
var err error
_, ok := err.(interface{ IsSet() bool })
println(ok)
}
// Output:
// false

16
_test/composite5.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import "fmt"
type T struct {
m uint16
}
var t = T{1<<2 | 1<<3}
func main() {
fmt.Println(t)
}
// Output:
// {12}

20
_test/composite6.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import (
"fmt"
"github.com/containous/yaegi/_test/ct1"
)
type T struct {
m uint16
}
var t = T{1 << ct1.R}
func main() {
fmt.Println(t)
}
// Output:
// {2}

22
_test/const6.go Normal file
View File

@@ -0,0 +1,22 @@
package main
const (
maxNonStarters = 30
maxBufferSize = maxNonStarters + 2
)
type reorderBuffer struct {
rune [maxBufferSize]Properties
}
type Properties struct {
pos uint8
size uint8
}
func main() {
println(len(reorderBuffer{}.rune))
}
// Output:
// 32

19
_test/const7.go Normal file
View File

@@ -0,0 +1,19 @@
package main
import "fmt"
const (
a = iota
b
c
d
)
type T [c]int
func main() {
fmt.Println(T{})
}
// Output:
// [0 0]

3
_test/ct/ct1.go Normal file
View File

@@ -0,0 +1,3 @@
package ct
func init() { println("hello from ct1") }

5
_test/ct/ct2.go Normal file
View File

@@ -0,0 +1,5 @@
// +build !dummy
package ct
func init() { println("hello from ct2") }

5
_test/ct/ct3.go Normal file
View File

@@ -0,0 +1,5 @@
// +build dummy
package ct
func init() { println("hello from ct3") }

9
_test/ct1/ct1.go Normal file
View File

@@ -0,0 +1,9 @@
package ct1
type Class uint
const (
L Class = iota
R
AL
)

3
_test/foo-bar/foo-bar.go Normal file
View File

@@ -0,0 +1,3 @@
package bar
var Name = "foo-bar"

9
_test/for7.go Normal file
View File

@@ -0,0 +1,9 @@
package main
func main() {
for i := 0; i; {
}
}
// Error:
// 4:14: non-bool used as for condition

18
_test/for8.go Normal file
View File

@@ -0,0 +1,18 @@
package main
func main() {
for i := 0; i < 4; i++ {
for {
break
}
if i == 1 {
continue
}
println(i)
}
}
// Output:
// 0
// 2
// 3

19
_test/fun10.go Normal file
View File

@@ -0,0 +1,19 @@
package main
import "fmt"
func f() func() {
return nil
}
func main() {
g := f()
fmt.Printf("%T %v\n", g, g)
if g == nil {
fmt.Println("nil func")
}
}
// Output:
// func() <nil>
// nil func

12
_test/fun11.go Normal file
View File

@@ -0,0 +1,12 @@
package main
var f F
type F func(int)
func main() {
println("ok")
}
// Output:
// ok

13
_test/if2.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import "fmt"
func main() {
var i int
if i % 1000000 {
fmt.Println("oops")
}
}
// Error:
// 7:5: non-bool used as if condition

10
_test/import7.go Normal file
View File

@@ -0,0 +1,10 @@
package main
import "github.com/containous/yaegi/_test/foo-bar"
func main() {
println(bar.Name)
}
// Output:
// foo-bar

10
_test/import8.go Normal file
View File

@@ -0,0 +1,10 @@
package main
import "github.com/containous/yaegi/_test/b1/foo"
func main() {
println(foo.Desc)
}
// Output:
// in b1/foo

18
_test/interface12.go Normal file
View File

@@ -0,0 +1,18 @@
package main
type I1 interface {
Truc()
}
type T1 struct{}
func (T1) Truc() { println("in T1 truc") }
var x I1 = T1{}
func main() {
x.Truc()
}
// Output:
// in T1 truc

32
_test/interface13.go Normal file
View File

@@ -0,0 +1,32 @@
package main
import (
"fmt"
)
type X struct{}
func (X) Foo() int {
return 1
}
func (X) Bar() int {
return 2
}
type Foo interface {
Foo() int
}
type Bar interface {
Bar() int
}
func main() {
var x X
var i Foo = x
j := i.(Bar)
fmt.Println(j.Bar())
}
// Output:
// 2

17
_test/interface14.go Normal file
View File

@@ -0,0 +1,17 @@
package main
type T struct{}
func (t *T) Error() string { return "T: error" }
var invalidT = &T{}
func main() {
var err error
if err != invalidT {
println("ok")
}
}
// Output:
// ok

28
_test/interface15.go Normal file
View File

@@ -0,0 +1,28 @@
package main
type Fooer interface {
Foo() string
}
type Barer interface {
//fmt.Stringer
Fooer
Bar()
}
type T struct{}
func (t *T) Foo() string { return "T: foo" }
func (*T) Bar() { println("in bar") }
var t = &T{}
func main() {
var f Barer
if f != t {
println("ok")
}
}
// Output:
// ok

25
_test/interface16.go Normal file
View File

@@ -0,0 +1,25 @@
package main
import "fmt"
type Barer interface {
fmt.Stringer
Bar()
}
type T struct{}
func (*T) String() string { return "T: nothing" }
func (*T) Bar() { println("in bar") }
var t = &T{}
func main() {
var f Barer
if f != t {
println("ok")
}
}
// Output:
// ok

17
_test/interface17.go Normal file
View File

@@ -0,0 +1,17 @@
package main
type T struct{}
func (t T) Error() string { return "T: error" }
var invalidT = T{}
func main() {
var err error
if err != invalidT {
println("ok")
}
}
// Output:
// ok

18
_test/interface18.go Normal file
View File

@@ -0,0 +1,18 @@
package main
type T struct{}
func (t *T) Error() string { return "T: error" }
func (*T) Foo() { println("foo") }
var invalidT = &T{}
func main() {
var err error
if err != invalidT {
println("ok")
}
}
// Output:
// ok

12
_test/interface19.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import "fmt"
var I interface{}
func main() {
fmt.Printf("%T %v\n", I, I)
}
// Output:
// <nil> <nil>

16
_test/map19.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import "fmt"
type cmap struct {
servers map[int64]*server
}
type server struct {
cm *cmap
}
func main() {
m := cmap{}
fmt.Println(m)
}

10
_test/map20.go Normal file
View File

@@ -0,0 +1,10 @@
package main
var a = map[int]bool{1: true, 2: true}
func main() {
println(a[1] && true)
}
// Output:
// true

13
_test/math1.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import "math"
func main() {
var f float32
if f < math.MaxFloat32 {
println("ok")
}
}
// Output:
// ok

18
_test/method28.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import "fmt"
type T struct {
Name string
}
func (T) create() *T {
return &T{"Hello"}
}
func main() {
fmt.Println(T{}.create())
}
// Output:
// &{Hello}

16
_test/method29.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"context"
"net"
)
var lookupHost = net.DefaultResolver.LookupHost
func main() {
res, err := lookupHost(context.Background(), "localhost")
println(len(res) > 0, err == nil)
}
// Output:
// true true

20
_test/method30.go Normal file
View File

@@ -0,0 +1,20 @@
package main
type T struct {
Name string
}
func (t *T) foo(a string) string {
return t.Name + a
}
var g = &T{"global"}
var f = g.foo
func main() {
println(f("-x"))
}
// Output:
// global-x

29
_test/method31.go Normal file
View File

@@ -0,0 +1,29 @@
package main
import "fmt"
var db dbWrapper
type dbWrapper struct {
DB *cmap
}
func (d *dbWrapper) get() *cmap {
return d.DB
}
type cmap struct {
name string
}
func (c *cmap) f() {
fmt.Println("in f, c", c)
}
func main() {
db.DB = &cmap{name: "test"}
db.get().f()
}
// Output:
// in f, c &{test}

15
_test/nil0.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import "fmt"
func f() (host, port string, err error) {
return "", "", nil
}
func main() {
h, p, err := f()
fmt.Println(h, p, err)
}
// Output:
// <nil>

12
_test/nil1.go Normal file
View File

@@ -0,0 +1,12 @@
package main
func main() {
var a error = nil
if a == nil || a.Error() == "nil" {
println("a is nil")
}
}
// Output:
// a is nil

9
_test/op4.go Normal file
View File

@@ -0,0 +1,9 @@
package main
func main() {
i := 100
println(i % 1e2)
}
// Output:
// 0

9
_test/or0.go Normal file
View File

@@ -0,0 +1,9 @@
package main
func main() {
c := false
println(c || !c)
}
// Output:
// true

9
_test/or1.go Normal file
View File

@@ -0,0 +1,9 @@
package main
func main() {
c := false
println(!c || c)
}
// Output:
// true

10
_test/or2.go Normal file
View File

@@ -0,0 +1,10 @@
package main
var a = false || true
func main() {
println(a)
}
// Output:
// true

26
_test/primes.go Normal file
View File

@@ -0,0 +1,26 @@
package main
func Primes(n int) int {
var xs []int
for i := 2; len(xs) < n; i++ {
ok := true
for _, x := range xs {
if i%x == 0 {
ok = false
break
}
}
if !ok {
continue
}
xs = append(xs, i)
}
return xs[n-1]
}
func main() {
println(Primes(3))
}
// Output:
// 5

10
_test/ptr8.go Normal file
View File

@@ -0,0 +1,10 @@
package main
var a = func() *bool { b := true; return &b }()
func main() {
println(*a && true)
}
// Output:
// true

14
_test/range0.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
func main() {
v := []int{1, 2, 3}
for i := range v {
v = append(v, i)
}
fmt.Println(v)
}
// Output:
// [1 2 3 0 1 2]

14
_test/range1.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
func main() {
a := [...]int{2, 1, 0}
for _, v := range a {
a[v] = v
}
fmt.Println(a)
}
// Output:
// [0 1 2]

14
_test/range2.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
func main() {
a := [...]int{2, 1, 0}
for _, v := range &a {
a[v] = v
}
fmt.Println(a)
}
// Output:
// [2 1 2]

14
_test/range3.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
func main() {
m := map[int]bool{1: true, 3: true, 5: true}
for k := range m {
m[k*2] = true
}
fmt.Println("ok")
}
// Output:
// ok

15
_test/range4.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import "fmt"
func main() {
m := map[int]bool{1: true, 3: true, 5: true}
for _, v := range m {
fmt.Println(v)
}
}
// Output:
// true
// true
// true

15
_test/range5.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import "fmt"
func main() {
m := map[int]bool{1: true, 3: true, 5: true}
var n int
for range m {
n++
}
fmt.Println(n)
}
// Output:
// 3

18
_test/range6.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"math"
)
func main() {
m := map[float64]bool{math.NaN(): true, math.NaN(): true, math.NaN(): true}
for _, v := range m {
fmt.Println(v)
}
}
// Output:
// true
// true
// true

11
_test/ret7.go Normal file
View File

@@ -0,0 +1,11 @@
package main
func one() uint {
return 1
}
func main() {
println(one())
}
// Output:
// 1

11
_test/str3.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import "strconv"
func main() {
str := strconv.Itoa(101)
println(str[0] == '1')
}
// Output:
// true

11
_test/str4.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import "unicode/utf8"
func main() {
r, _ := utf8.DecodeRuneInString("Hello")
println(r < utf8.RuneSelf)
}
// Output:
// true

19
_test/struct29.go Normal file
View File

@@ -0,0 +1,19 @@
package main
type T1 struct {
A []T2
B []T2
}
type T2 struct {
name string
}
var t = T1{}
func main() {
println("ok")
}
// Output:
// ok

19
_test/struct30.go Normal file
View File

@@ -0,0 +1,19 @@
package main
type T1 struct {
A []T2
M map[uint64]T2
}
type T2 struct {
name string
}
var t = T1{}
func main() {
println("ok")
}
// Output:
// ok

14
_test/struct31.go Normal file
View File

@@ -0,0 +1,14 @@
package main
type T struct {
bool
}
var t = T{true}
func main() {
println(t.bool && true)
}
// Output:
// true

17
_test/switch21.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "fmt"
func main() {
var err error
switch v := err.(type) {
case fmt.Formatter:
println("formatter")
default:
fmt.Println(v)
}
}
// Output:
// <nil>

21
_test/switch22.go Normal file
View File

@@ -0,0 +1,21 @@
package main
type T struct {
Name string
}
func f(t interface{}) {
switch ext := t.(type) {
case *T:
println("*T", ext.Name)
default:
println("unknown")
}
}
func main() {
f(&T{"truc"})
}
// Output:
// *T truc

15
_test/tag0.go Normal file
View File

@@ -0,0 +1,15 @@
// The following comment line has the same effect as 'go run -tags=dummy'
//yaegi:tags dummy
package main
import _ "github.com/containous/yaegi/_test/ct"
func main() {
println("bye")
}
// Output:
// hello from ct1
// hello from ct3
// bye

13
_test/time10.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import "time"
var UnixTime func(int64, int64) time.Time
func main() {
UnixTime = time.Unix
println(UnixTime(1e9, 0).In(time.UTC).Minute())
}
// Output:
// 46

15
_test/time11.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import (
"fmt"
"time"
)
const df = time.Minute * 30
func main() {
fmt.Printf("df: %v %T\n", df, df)
}
// Output:
// df: 30m0s time.Duration

13
_test/time9.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println((5 * time.Minute).Seconds())
}
// Output:
// 300

9
_test/type15.go Normal file
View File

@@ -0,0 +1,9 @@
package main
func main() {
err := error(nil)
println(err == nil)
}
// Output:
// true

11
_test/type16.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import "fmt"
func main() {
a := uint8(15) ^ byte(0)
fmt.Printf("%T %v\n", a, a)
}
// Output:
// uint8 15

11
_test/type17.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import "fmt"
func main() {
a := int32(15) ^ rune(0)
fmt.Printf("%T %v\n", a, a)
}
// Output:
// int32 15

11
_test/var10.go Normal file
View File

@@ -0,0 +1,11 @@
package main
var _ = true
var _ = "hello"
func main() {
println("hello")
}
// Output:
// hello

10
_test/var11.go Normal file
View File

@@ -0,0 +1,10 @@
package main
var a, _, _, b = 1, true, "foo", 2
func main() {
println(a, b)
}
// Output:
// 1 2

11
_test/var9.go Normal file
View File

@@ -0,0 +1,11 @@
package main
var a = "sdofjsdfj"
var z = a[0:2]
func main() {
println(z)
}
// Output:
// sd

View File

@@ -1,7 +1,7 @@
//go:generate go build //go:generate go build
/* /*
Goexports generates wrappers of package exported symbols Goexports generates wrappers of package exported symbols.
Output files are written in the current directory, and prefixed with the go version. Output files are written in the current directory, and prefixed with the go version.
@@ -26,6 +26,7 @@ import (
"go/constant" "go/constant"
"go/format" "go/format"
"go/importer" "go/importer"
"go/token"
"go/types" "go/types"
"io/ioutil" "io/ioutil"
"log" "log"
@@ -114,7 +115,7 @@ type Wrap struct {
} }
func genContent(dest, pkgName, license string) ([]byte, error) { func genContent(dest, pkgName, license string) ([]byte, error) {
p, err := importer.For("source", nil).Import(pkgName) p, err := importer.ForCompiler(token.NewFileSet(), "source", nil).Import(pkgName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -147,7 +148,12 @@ func genContent(dest, pkgName, license string) ([]byte, error) {
pname := path.Base(pkgName) + "." + name pname := path.Base(pkgName) + "." + name
switch o := o.(type) { switch o := o.(type) {
case *types.Const: case *types.Const:
val[name] = Val{fixConst(pname, o.Val()), false} if b, ok := o.Type().(*types.Basic); ok && (b.Info()&types.IsUntyped) != 0 {
// convert untyped constant to right type to avoid overflow
val[name] = Val{fixConst(pname, o.Val()), false}
} else {
val[name] = Val{pname, false}
}
case *types.Func: case *types.Func:
val[name] = Val{pname, false} val[name] = Val{pname, false}
case *types.Var: case *types.Var:
@@ -248,7 +254,14 @@ func genContent(dest, pkgName, license string) ([]byte, error) {
// fixConst checks untyped constant value, converting it if necessary to avoid overflow // fixConst checks untyped constant value, converting it if necessary to avoid overflow
func fixConst(name string, val constant.Value) string { func fixConst(name string, val constant.Value) string {
if val.Kind() == constant.Int { switch val.Kind() {
case constant.Float:
str := val.ExactString()
if _, err := strconv.ParseFloat(str, 32); err == nil {
return "float32(" + name + ")"
}
return name
case constant.Int:
str := val.ExactString() str := val.ExactString()
i, err := strconv.ParseInt(str, 0, 64) i, err := strconv.ParseInt(str, 0, 64)
if err == nil { if err == nil {
@@ -265,8 +278,10 @@ func fixConst(name string, val constant.Value) string {
if err == nil { if err == nil {
return "uint64(" + name + ")" return "uint64(" + name + ")"
} }
return name
default:
return name
} }
return name
} }
// genLicense generates the correct LICENSE header text from the provided // genLicense generates the correct LICENSE header text from the provided

View File

@@ -8,7 +8,7 @@ If invoked with no arguments, it processes the standard input
in a Read-Eval-Print-Loop. A prompt is displayed if standard input in a Read-Eval-Print-Loop. A prompt is displayed if standard input
is a terminal. is a terminal.
Given a file, it operates on that file. if the first line starts with Given a file, it operates on that file. If the first line starts with
"#!/usr/bin/env yaegi", and the file has exec permission, then the file "#!/usr/bin/env yaegi", and the file has exec permission, then the file
can be invoked directly from the shell. can be invoked directly from the shell.
@@ -18,7 +18,10 @@ at global level in an implicit main package.
Options: Options:
-i -i
start an interactive REPL after file execution start an interactive REPL after file execution.
-tags tag,list
a comma-separated list of build tags to consider satisfied during
the interpretation.
Debugging support (may be removed at any time): Debugging support (may be removed at any time):
YAEGI_AST_DOT=1 YAEGI_AST_DOT=1
@@ -43,7 +46,9 @@ import (
func main() { func main() {
var interactive bool var interactive bool
var tags string
flag.BoolVar(&interactive, "i", false, "start an interactive REPL") flag.BoolVar(&interactive, "i", false, "start an interactive REPL")
flag.StringVar(&tags, "tags", "", "set a list of build tags")
flag.Usage = func() { flag.Usage = func() {
fmt.Println("Usage:", os.Args[0], "[options] [script] [args]") fmt.Println("Usage:", os.Args[0], "[options] [script] [args]")
fmt.Println("Options:") fmt.Println("Options:")
@@ -53,7 +58,7 @@ func main() {
args := flag.Args() args := flag.Args()
log.SetFlags(log.Lshortfile) log.SetFlags(log.Lshortfile)
i := interp.New(interp.Options{GoPath: build.Default.GOPATH}) i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
i.Use(stdlib.Symbols) i.Use(stdlib.Symbols)
i.Use(interp.Symbols) i.Use(interp.Symbols)
@@ -79,9 +84,9 @@ func main() {
} }
if interactive { if interactive {
i.Repl(os.Stdin, os.Stdout) i.REPL(os.Stdin, os.Stdout)
} }
} else { } else {
i.Repl(os.Stdin, os.Stdout) i.REPL(os.Stdin, os.Stdout)
} }
} }

91
cmd/yaegi/yaegi_test.go Normal file
View File

@@ -0,0 +1,91 @@
package main
import (
"bytes"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"testing"
"time"
)
func TestYaegiCmdCancel(t *testing.T) {
tmp, err := ioutil.TempDir("", "yaegi-")
if err != nil {
t.Fatalf("failed to create tmp directory: %v", err)
}
defer func() {
err = os.RemoveAll(tmp)
if err != nil {
t.Errorf("failed to clean up %v: %v", tmp, err)
}
}()
yaegi := filepath.Join(tmp, "yaegi")
build := exec.Command("go", "build", "-race", "-o", yaegi, ".")
err = build.Run()
if err != nil {
t.Fatalf("failed to build yaegi command: %v", err)
}
// Test src must be terminated by a single newline.
tests := []string{
"for {}\n",
"select {}\n",
}
for _, src := range tests {
cmd := exec.Command(yaegi)
in, err := cmd.StdinPipe()
if err != nil {
t.Errorf("failed to get stdin pipe to yaegi command: %v", err)
}
var outBuf, errBuf bytes.Buffer
cmd.Stdout = &outBuf
cmd.Stderr = &errBuf
// https://golang.org/doc/articles/race_detector.html#Options
cmd.Env = []string{`GORACE="halt_on_error=1"`}
err = cmd.Start()
if err != nil {
t.Fatalf("failed to start yaegi command: %v", err)
}
_, err = in.Write([]byte(src))
if err != nil {
t.Errorf("failed pipe test source to yaegi command: %v", err)
}
time.Sleep(100 * time.Millisecond)
err = cmd.Process.Signal(os.Interrupt)
if err != nil {
t.Errorf("failed to send os.Interrupt to yaegi command: %v", err)
}
_, err = in.Write([]byte("1+1\n"))
if err != nil {
t.Errorf("failed to probe race: %v", err)
}
err = in.Close()
if err != nil {
t.Errorf("failed to close stdin pipe: %v", err)
}
err = cmd.Wait()
if err != nil {
if cmd.ProcessState.ExitCode() == 66 { // See race_detector.html article.
t.Errorf("race detected running yaegi command canceling %q: %v", src, err)
if testing.Verbose() {
t.Log(&errBuf)
}
} else {
t.Errorf("error running yaegi command for %q: %v", src, err)
}
continue
}
if outBuf.String() != "context canceled\n2\n" {
t.Errorf("unexpected output: %q", &outBuf)
}
}
}

View File

@@ -69,7 +69,6 @@ func TestPackages(t *testing.T) {
for _, test := range testCases { for _, test := range testCases {
test := test test := test
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
goPath, err := filepath.Abs(test.goPath) goPath, err := filepath.Abs(test.goPath)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -116,7 +115,6 @@ func TestPackagesError(t *testing.T) {
for _, test := range testCases { for _, test := range testCases {
test := test test := test
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
// Init go interpreter // Init go interpreter
i := interp.New(interp.Options{GoPath: test.goPath}) i := interp.New(interp.Options{GoPath: test.goPath})
i.Use(stdlib.Symbols) // Use binary standard library i.Use(stdlib.Symbols) // Use binary standard library

2
go.mod
View File

@@ -1,3 +1,3 @@
module github.com/containous/yaegi module github.com/containous/yaegi
go 1.11 go 1.12

View File

@@ -10,7 +10,7 @@ import (
const model = `package interp const model = `package interp
// Code generated by 'go run ../cmd/genop/genop.go'. DO NOT EDIT. // Code generated by 'go run ../internal/genop/genop.go'. DO NOT EDIT.
import "reflect" import "reflect"
@@ -352,6 +352,30 @@ func {{$name}}(n *node) {
} }
} }
{{end}} {{end}}
{{range $name, $op := .Unary}}
func {{$name}}Const(n *node) {
t := n.typ.rtype
v := n.child[0].rval
n.rval = reflect.New(t).Elem()
{{- if $op.Bool}}
n.rval.SetBool({{$op.Name}} v.Bool())
{{- else}}
switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
n.rval.SetInt({{$op.Name}} v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
n.rval.SetUint({{$op.Name}} v.Uint())
{{- if $op.Float}}
case reflect.Float32, reflect.Float64:
n.rval.SetFloat({{$op.Name}} v.Float())
case reflect.Complex64, reflect.Complex128:
n.rval.SetComplex({{$op.Name}} v.Complex())
{{- end}}
}
{{- end}}
}
{{end}}
{{range $name, $op := .Comparison}} {{range $name, $op := .Comparison}}
func {{$name}}(n *node) { func {{$name}}(n *node) {
tnext := getExec(n.tnext) tnext := getExec(n.tnext)
@@ -427,148 +451,6 @@ func {{$name}}(n *node) {
} }
} }
} }
{{- if $op.Complex}}
case isComplex(t0) || isComplex(t1):
switch {
case c0.rval.IsValid():
s0 := vComplex(c0.rval)
v1 := genValueComplex(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
_, s1 := v1(f)
if s0 {{$op.Name}} s1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
n.exec = func(f *frame) bltn {
_, s1 := v1(f)
dest(f).SetBool(s0 {{$op.Name}} s1)
return tnext
}
}
case c1.rval.IsValid():
s1 := vComplex(c1.rval)
v0 := genValueComplex(c0)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
_, s0 := v0(f)
if s0 {{$op.Name}} s1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
_, s0 := v0(f)
dest(f).SetBool(s0 {{$op.Name}} s1)
return tnext
}
}
default:
v0 := genValueComplex(n.child[0])
v1 := genValueComplex(n.child[1])
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
_, s0 := v0(f)
_, s1 := v1(f)
if s0 {{$op.Name}} s1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
n.exec = func(f *frame) bltn {
_, s0 := v0(f)
_, s1 := v1(f)
dest(f).SetBool(s0 {{$op.Name}} s1)
return tnext
}
}
}
default:
switch {
case c0.rval.IsValid():
i0 := c0.rval.Interface()
v1 := genValue(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
if i0 {{$op.Name}} i1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
dest(f).SetBool(i0 {{$op.Name}} i1)
return tnext
}
}
case c1.rval.IsValid():
i1 := c1.rval.Interface()
v0 := genValue(c0)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
if i0 {{$op.Name}} i1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
dest(f).SetBool(i0 {{$op.Name}} i1)
return tnext
}
}
default:
v0 := genValue(c0)
v1 := genValue(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
if i0 {{$op.Name}} i1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
dest(f).SetBool(i0 {{$op.Name}} i1)
return tnext
}
}
}
{{- end}}
case isFloat(t0) || isFloat(t1): case isFloat(t0) || isFloat(t1):
switch { switch {
case c0.rval.IsValid(): case c0.rval.IsValid():
@@ -781,6 +663,148 @@ func {{$name}}(n *node) {
} }
} }
} }
{{- if $op.Complex}}
case isComplex(t0) || isComplex(t1):
switch {
case c0.rval.IsValid():
s0 := vComplex(c0.rval)
v1 := genComplex(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
s1 := v1(f)
if s0 {{$op.Name}} s1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
n.exec = func(f *frame) bltn {
s1 := v1(f)
dest(f).SetBool(s0 {{$op.Name}} s1)
return tnext
}
}
case c1.rval.IsValid():
s1 := vComplex(c1.rval)
v0 := genComplex(c0)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
s0 := v0(f)
if s0 {{$op.Name}} s1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
s0 := v0(f)
dest(f).SetBool(s0 {{$op.Name}} s1)
return tnext
}
}
default:
v0 := genComplex(n.child[0])
v1 := genComplex(n.child[1])
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
s0 := v0(f)
s1 := v1(f)
if s0 {{$op.Name}} s1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
n.exec = func(f *frame) bltn {
s0 := v0(f)
s1 := v1(f)
dest(f).SetBool(s0 {{$op.Name}} s1)
return tnext
}
}
}
default:
switch {
case c0.rval.IsValid():
i0 := c0.rval.Interface()
v1 := genValue(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
if i0 {{$op.Name}} i1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
dest(f).SetBool(i0 {{$op.Name}} i1)
return tnext
}
}
case c1.rval.IsValid():
i1 := c1.rval.Interface()
v0 := genValue(c0)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
if i0 {{$op.Name}} i1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
dest(f).SetBool(i0 {{$op.Name}} i1)
return tnext
}
}
default:
v0 := genValue(c0)
v1 := genValue(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
if i0 {{$op.Name}} i1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
dest(f).SetBool(i0 {{$op.Name}} i1)
return tnext
}
}
}
{{- end}}
} }
} }
{{end}} {{end}}
@@ -793,6 +817,7 @@ type Op struct {
Float bool // true if operator applies to float Float bool // true if operator applies to float
Complex bool // true if operator applies to complex Complex bool // true if operator applies to complex
Shift bool // true if operator is a shift operation Shift bool // true if operator is a shift operation
Bool bool // true if operator applies to bool
} }
func main() { func main() {
@@ -805,17 +830,17 @@ func main() {
b := &bytes.Buffer{} b := &bytes.Buffer{}
data := map[string]interface{}{ data := map[string]interface{}{
"Arithmetic": map[string]Op{ "Arithmetic": map[string]Op{
"add": {"+", true, true, true, false}, "add": {"+", true, true, true, false, false},
"sub": {"-", false, true, true, false}, "sub": {"-", false, true, true, false, false},
"mul": {"*", false, true, true, false}, "mul": {"*", false, true, true, false, false},
"quo": {"/", false, true, true, false}, "quo": {"/", false, true, true, false, false},
"rem": {"%", false, false, false, false}, "rem": {"%", false, false, false, false, false},
"shl": {"<<", false, false, false, true}, "shl": {"<<", false, false, false, true, false},
"shr": {">>", false, false, false, true}, "shr": {">>", false, false, false, true, false},
"and": {"&", false, false, false, false}, "and": {"&", false, false, false, false, false},
"or": {"|", false, false, false, false}, "or": {"|", false, false, false, false, false},
"xor": {"^", false, false, false, false}, "xor": {"^", false, false, false, false, false},
"andNot": {"&^", false, false, false, false}, "andNot": {"&^", false, false, false, false, false},
}, },
"IncDec": map[string]Op{ "IncDec": map[string]Op{
"inc": {Name: "+"}, "inc": {Name: "+"},
@@ -829,6 +854,12 @@ func main() {
"lowerEqual": {Name: "<=", Complex: false}, "lowerEqual": {Name: "<=", Complex: false},
"notEqual": {Name: "!=", Complex: true}, "notEqual": {Name: "!=", Complex: true},
}, },
"Unary": map[string]Op{
"not": {Name: "!", Float: false, Bool: true},
"neg": {Name: "-", Float: true, Bool: false},
"pos": {Name: "+", Float: true, Bool: false},
"bitNot": {Name: "^", Float: false, Bool: false},
},
} }
if err = parse.Execute(b, data); err != nil { if err = parse.Execute(b, data); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@@ -6,8 +6,10 @@ import (
"go/parser" "go/parser"
"go/scanner" "go/scanner"
"go/token" "go/token"
"math"
"reflect" "reflect"
"strconv" "strconv"
"sync/atomic"
) )
// nkind defines the kind of AST, i.e. the grammar category // nkind defines the kind of AST, i.e. the grammar category
@@ -73,8 +75,6 @@ const (
parenExpr parenExpr
rangeStmt rangeStmt
returnStmt returnStmt
rvalueExpr
rtypeExpr
selectStmt selectStmt
selectorExpr selectorExpr
selectorImport selectorImport
@@ -152,8 +152,6 @@ var kinds = [...]string{
parenExpr: "parenExpr", parenExpr: "parenExpr",
rangeStmt: "rangeStmt", rangeStmt: "rangeStmt",
returnStmt: "returnStmt", returnStmt: "returnStmt",
rvalueExpr: "rvalueExpr",
rtypeExpr: "rtypeExpr",
selectStmt: "selectStmt", selectStmt: "selectStmt",
selectorExpr: "selectorExpr", selectorExpr: "selectorExpr",
selectorImport: "selectorImport", selectorImport: "selectorImport",
@@ -197,6 +195,7 @@ const (
aAndAssign aAndAssign
aAndNot aAndNot
aAndNotAssign aAndNotAssign
aBitNot
aCall aCall
aCase aCase
aCompositeLit aCompositeLit
@@ -215,11 +214,12 @@ const (
aMethod aMethod
aMul aMul
aMulAssign aMulAssign
aNegate aNeg
aNot aNot
aNotEqual aNotEqual
aOr aOr
aOrAssign aOrAssign
aPos
aQuo aQuo
aQuoAssign aQuoAssign
aRange aRange
@@ -253,6 +253,7 @@ var actions = [...]string{
aAndAssign: "&=", aAndAssign: "&=",
aAndNot: "&^", aAndNot: "&^",
aAndNotAssign: "&^=", aAndNotAssign: "&^=",
aBitNot: "^",
aCall: "call", aCall: "call",
aCase: "case", aCase: "case",
aCompositeLit: "compositeLit", aCompositeLit: "compositeLit",
@@ -269,11 +270,12 @@ var actions = [...]string{
aMethod: "Method", aMethod: "Method",
aMul: "*", aMul: "*",
aMulAssign: "*=", aMulAssign: "*=",
aNegate: "-", aNeg: "-",
aNot: "!", aNot: "!",
aNotEqual: "!=", aNotEqual: "!=",
aOr: "|", aOr: "|",
aOrAssign: "|=", aOrAssign: "|=",
aPos: "+",
aQuo: "/", aQuo: "/",
aQuoAssign: "/=", aQuoAssign: "/=",
aRange: "range", aRange: "range",
@@ -320,6 +322,7 @@ func (interp *Interpreter) firstToken(src string) token.Token {
func (interp *Interpreter) ast(src, name string) (string, *node, error) { func (interp *Interpreter) ast(src, name string) (string, *node, error) {
inRepl := name == "" inRepl := name == ""
var inFunc bool var inFunc bool
var mode parser.Mode
// Allow incremental parsing of declarations or statements, by inserting // Allow incremental parsing of declarations or statements, by inserting
// them in a pseudo file package or function. Those statements or // them in a pseudo file package or function. Those statements or
@@ -334,26 +337,30 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
inFunc = true inFunc = true
src = "package main; func main() {" + src + "}" src = "package main; func main() {" + src + "}"
} }
// Parse comments in REPL mode, to allow tag setting
mode |= parser.ParseComments
} }
if ok, err := interp.buildOk(interp.context, name, src); !ok || err != nil { 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, 0) f, err := parser.ParseFile(interp.fset, name, src, mode)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
setYaegiTags(&interp.context, f.Comments)
var root *node var root *node
var anc astNode var anc astNode
var st nodestack var st nodestack
var pkgName string var pkgName string
addChild := func(root **node, anc astNode, pos token.Pos, kind nkind, act action) *node { addChild := func(root **node, anc astNode, pos token.Pos, kind nkind, act action) *node {
interp.nindex++
var i interface{} var i interface{}
n := &node{anc: anc.node, interp: interp, index: interp.nindex, pos: pos, kind: kind, action: act, val: &i, gen: builtin[act]} nindex := atomic.AddInt64(&interp.nindex, 1)
n := &node{anc: anc.node, interp: interp, index: nindex, pos: pos, kind: kind, action: act, val: &i, gen: builtin[act]}
n.start = n n.start = n
if anc.node == nil { if anc.node == nil {
*root = n *root = n
@@ -364,15 +371,15 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
if len(ancAst.List)+len(ancAst.Body) == len(anc.node.child) { if len(ancAst.List)+len(ancAst.Body) == len(anc.node.child) {
// All case clause children are collected. // All case clause children are collected.
// Split children in condition and body nodes to desambiguify the AST. // Split children in condition and body nodes to desambiguify the AST.
interp.nindex++ nindex = atomic.AddInt64(&interp.nindex, 1)
body := &node{anc: anc.node, interp: interp, index: interp.nindex, pos: pos, kind: caseBody, action: aNop, val: &i, gen: nop} body := &node{anc: anc.node, interp: interp, index: nindex, pos: pos, kind: caseBody, action: aNop, val: &i, gen: nop}
if ts := anc.node.anc.anc; ts.kind == typeSwitch && ts.child[1].action == aAssign { if ts := anc.node.anc.anc; ts.kind == typeSwitch && ts.child[1].action == aAssign {
// In type switch clause, if a switch guard is assigned, duplicate the switch guard symbol // In type switch clause, if a switch guard is assigned, duplicate the switch guard symbol
// in each clause body, so a different guard type can be set in each clause // in each clause body, so a different guard type can be set in each clause
name := ts.child[1].child[0].ident name := ts.child[1].child[0].ident
interp.nindex++ nindex = atomic.AddInt64(&interp.nindex, 1)
gn := &node{anc: body, interp: interp, ident: name, index: interp.nindex, pos: pos, kind: identExpr, action: aNop, val: &i, gen: nop} gn := &node{anc: body, interp: interp, ident: name, index: nindex, pos: pos, kind: identExpr, action: aNop, val: &i, gen: nop}
body.child = append(body.child, gn) body.child = append(body.child, gn)
} }
@@ -459,7 +466,11 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
n.rval = reflect.ValueOf(v) n.rval = reflect.ValueOf(v)
case token.FLOAT: case token.FLOAT:
v, _ := strconv.ParseFloat(a.Value, 64) v, _ := strconv.ParseFloat(a.Value, 64)
n.rval = reflect.ValueOf(v) if math.Trunc(v) == v {
n.rval = reflect.ValueOf(int(v))
} else {
n.rval = reflect.ValueOf(v)
}
case token.IMAG: case token.IMAG:
v, _ := strconv.ParseFloat(a.Value[:len(a.Value)-1], 64) v, _ := strconv.ParseFloat(a.Value[:len(a.Value)-1], 64)
n.rval = reflect.ValueOf(complex(0, v)) n.rval = reflect.ValueOf(complex(0, v))
@@ -548,6 +559,9 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
case *ast.CommClause: case *ast.CommClause:
st.push(addChild(&root, anc, pos, commClause, aNop), nod) st.push(addChild(&root, anc, pos, commClause, aNop), nod)
case *ast.CommentGroup:
return false
case *ast.CompositeLit: case *ast.CompositeLit:
st.push(addChild(&root, anc, pos, compositeLitExpr, aCompositeLit), nod) st.push(addChild(&root, anc, pos, compositeLitExpr, aCompositeLit), nod)
@@ -757,6 +771,8 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
var kind = unaryExpr var kind = unaryExpr
var act action var act action
switch a.Op { switch a.Op {
case token.ADD:
act = aPos
case token.AND: case token.AND:
kind = addressExpr kind = addressExpr
act = aAddr act = aAddr
@@ -765,7 +781,9 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
case token.NOT: case token.NOT:
act = aNot act = aNot
case token.SUB: case token.SUB:
act = aNegate act = aNeg
case token.XOR:
act = aBitNot
} }
st.push(addChild(&root, anc, pos, kind, act), nod) st.push(addChild(&root, anc, pos, kind, act), nod)
@@ -839,9 +857,9 @@ func (s *nodestack) top() astNode {
// dup returns a duplicated node subtree // dup returns a duplicated node subtree
func (interp *Interpreter) dup(nod, anc *node) *node { func (interp *Interpreter) dup(nod, anc *node) *node {
interp.nindex++ nindex := atomic.AddInt64(&interp.nindex, 1)
n := *nod n := *nod
n.index = interp.nindex n.index = nindex
n.anc = anc n.anc = anc
n.start = &n n.start = &n
n.pos = anc.pos n.pos = anc.pos

View File

@@ -1,6 +1,7 @@
package interp package interp
import ( import (
"go/ast"
"go/build" "go/build"
"go/parser" "go/parser"
"path" "path"
@@ -11,7 +12,7 @@ import (
// buildOk returns true if a file or script matches build constraints // buildOk returns true if a file or script matches build constraints
// as specified in https://golang.org/pkg/go/build/#hdr-Build_Constraints. // as specified in https://golang.org/pkg/go/build/#hdr-Build_Constraints.
// An error from parser is returned as well. // An error from parser is returned as well.
func (interp *Interpreter) buildOk(ctx build.Context, name, src string) (bool, error) { func (interp *Interpreter) buildOk(ctx *build.Context, name, src string) (bool, error) {
// Extract comments before the first clause // Extract comments before the first clause
f, err := parser.ParseFile(interp.fset, name, src, parser.PackageClauseOnly|parser.ParseComments) f, err := parser.ParseFile(interp.fset, name, src, parser.PackageClauseOnly|parser.ParseComments)
if err != nil { if err != nil {
@@ -25,12 +26,13 @@ func (interp *Interpreter) buildOk(ctx build.Context, name, src string) (bool, e
} }
} }
} }
setYaegiTags(ctx, f.Comments)
return true, nil return true, nil
} }
// buildLineOk returns true if line is not a build constraint or // buildLineOk returns true if line is not a build constraint or
// if build constraint is satisfied // if build constraint is satisfied
func buildLineOk(ctx build.Context, line string) (ok bool) { func buildLineOk(ctx *build.Context, line string) (ok bool) {
if len(line) < 7 || line[:7] != "+build " { if len(line) < 7 || line[:7] != "+build " {
return true return true
} }
@@ -45,7 +47,7 @@ func buildLineOk(ctx build.Context, line string) (ok bool) {
} }
// buildOptionOk return true if all comma separated tags match, false otherwise // buildOptionOk return true if all comma separated tags match, false otherwise
func buildOptionOk(ctx build.Context, tag string) bool { func buildOptionOk(ctx *build.Context, tag string) bool {
// in option, evaluate the AND of individual tags // in option, evaluate the AND of individual tags
for _, t := range strings.Split(tag, ",") { for _, t := range strings.Split(tag, ",") {
if !buildTagOk(ctx, t) { if !buildTagOk(ctx, t) {
@@ -57,7 +59,7 @@ func buildOptionOk(ctx build.Context, tag string) bool {
// buildTagOk returns true if a build tag matches, false otherwise // buildTagOk returns true if a build tag matches, false otherwise
// if first character is !, result is negated // if first character is !, result is negated
func buildTagOk(ctx build.Context, s string) (r bool) { func buildTagOk(ctx *build.Context, s string) (r bool) {
not := s[0] == '!' not := s[0] == '!'
if not { if not {
s = s[1:] s = s[1:]
@@ -82,6 +84,25 @@ func buildTagOk(ctx build.Context, s string) (r bool) {
return return
} }
// setYaegiTags scans a comment group for "yaegi:tags tag1 tag2 ..." lines
// and adds the corresponding tags to the interpreter build tags.
func setYaegiTags(ctx *build.Context, comments []*ast.CommentGroup) {
for _, g := range comments {
for _, line := range strings.Split(strings.TrimSpace(g.Text()), "\n") {
if len(line) < 11 || line[:11] != "yaegi:tags " {
continue
}
tags := strings.Split(strings.TrimSpace(line[10:]), " ")
for _, tag := range tags {
if !contains(ctx.BuildTags, tag) {
ctx.BuildTags = append(ctx.BuildTags, tag)
}
}
}
}
}
func contains(tags []string, tag string) bool { func contains(tags []string, tag string) bool {
for _, t := range tags { for _, t := range tags {
if t == tag { if t == tag {
@@ -92,7 +113,7 @@ func contains(tags []string, tag string) bool {
} }
// goMinorVersion returns the go minor version number // goMinorVersion returns the go minor version number
func goMinorVersion(ctx build.Context) int { func goMinorVersion(ctx *build.Context) int {
current := ctx.ReleaseTags[len(ctx.ReleaseTags)-1] current := ctx.ReleaseTags[len(ctx.ReleaseTags)-1]
v := strings.Split(current, ".") v := strings.Split(current, ".")
@@ -108,7 +129,7 @@ func goMinorVersion(ctx build.Context) int {
} }
// skipFile returns true if file should be skipped // skipFile returns true if file should be skipped
func skipFile(ctx build.Context, p string) bool { func skipFile(ctx *build.Context, p string) bool {
if !strings.HasSuffix(p, ".go") { if !strings.HasSuffix(p, ".go") {
return true return true
} }

View File

@@ -44,7 +44,7 @@ func TestBuildTag(t *testing.T) {
test := test test := test
src := test.src + "\npackage x" src := test.src + "\npackage x"
t.Run(test.src, func(t *testing.T) { t.Run(test.src, func(t *testing.T) {
if r, _ := i.buildOk(ctx, "", src); r != test.res { if r, _ := i.buildOk(&ctx, "", src); r != test.res {
t.Errorf("got %v, want %v", r, test.res) t.Errorf("got %v, want %v", r, test.res)
} }
}) })
@@ -74,7 +74,7 @@ func TestBuildFile(t *testing.T) {
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run(test.src, func(t *testing.T) { t.Run(test.src, func(t *testing.T) {
if r := skipFile(ctx, test.src); r != test.res { if r := skipFile(&ctx, test.src); r != test.res {
t.Errorf("got %v, want %v", r, test.res) t.Errorf("got %v, want %v", r, test.res)
} }
}) })
@@ -106,7 +106,7 @@ func Test_goMinorVersion(t *testing.T) {
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
minor := goMinorVersion(test.context) minor := goMinorVersion(&test.context)
if minor != test.expected { if minor != test.expected {
t.Errorf("got %v, want %v", minor, test.expected) t.Errorf("got %v, want %v", minor, test.expected)

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