Compare commits

...

46 Commits

Author SHA1 Message Date
mpl
563270ca02 interp: support yet another vendoring case
* interp: support another vendoring case

Namely, when the vendor dir is a sibling (or an uncle) relative to the
current pkg

Fixes #758

* make linter happier

* address review comments

* fix, cleanup, add unit tests

* add dummy files to force dirs into git
2020-07-15 15:35:04 +02:00
Marc Vertes
5eecbe515b fix: compositeSparse handles fields of interface kind
Fixes #776.
2020-07-13 17:55:04 +02:00
Marc Vertes
0a79069dfc fix: correct control flow graph for range init expression
The range init AST execution was skipped, and range could work
only over variables or direct function calls. By setting the
start node to the start of init and not init itself, we ensure
that the init AST is always taken into account.

Fixes #775.
2020-07-13 15:35:04 +02:00
Marc Vertes
0c8f538cd9 fix: apply method receiver offset when generating interface wrapper
Fixes #772.
2020-07-12 14:20:03 +02:00
Nicholas Wiersma
ca80ada849 fix: deal with untyped in type check 2020-07-10 11:55:04 +02:00
Nicholas Wiersma
3c6df504df fix: dont allow calling init 2020-07-09 14:35:04 +02:00
Nicholas Wiersma
98eacf3610 fix: execute global variables in the correct order
* fix: constant definition loop on out of order vars

* fix: do not wire global varDecl

* fix: wire and execute global vars

* chore: add tests

* fix: refactor and lint
2020-07-09 14:05:03 +02:00
Marc Vertes
16ff52a949 fix: avoid a panic in CFG in case of incomplete type
By returning early in case of incomplete type in CFG, we avoid
a panic, and let a chance to a new attempt after the missing
type has been parsed.

Fixes 763.
2020-07-09 13:05:04 +02:00
Marc Vertes
640d1429e5 fix: type assert when status is _
If the status is _, there is no storage allocated in frame, and
the status assign operation should be skipped.

Fixes #761.
2020-07-09 08:45:03 +02:00
Nicholas Wiersma
659913eebe fix: convert type properly to the correct type 2020-07-08 22:55:03 +02:00
Marc Vertes
b3766509cc feature: restrict symbols which can exit the interpreter process
* feature: restrict symbols which can exit the interpreter process

Some symbols such as os.Exit or log.Fatal, which make the current process
to exit, are now restricted. They are replaced by a version which panics
instead of exiting, as panics are recovered by Eval.

The restricted os.FindProcess version is identical to the original
except it errors when trying to return the self process, in order to
forbid killing or signaling the interpreter process from script.

The os/exec symbols are available only through unrestricted package.

The original symbols are stored in an unrestricted package, which
requires an explicit Use, as for unsafe and syscall packages.

The Use() interpreter method has been slightly modified to allow inplace
updating of package symbols, allowing to replace some symbols but not
the entire imported package.

A command line option -unrestricted has been added to yaegi CLI to use
the unrestricted symbols.

Fixes #486.

* fix: lint
2020-07-08 22:35:04 +02:00
Nicholas Wiersma
bc2b224bae fix: make a copy of defined before detecting recursivness 2020-07-07 12:05:03 +02:00
Nicholas Wiersma
9d4685deea fix: handle interfaces in composite sparce (#749)
Co-authored-by: Marc Vertes <mvertes@free.fr>
2020-07-06 15:41:27 +02:00
Marc Vertes
2a70a71dc2 fix: avoid infinite loop when parsing recursive types
Mark all visited types as such when walking down struct fields.

Fixes #750.
Updates #652.
2020-07-06 15:30:04 +02:00
Nicholas Wiersma
851444453c fix: assert switch type from valueT in struct case (#747)
* fix: switch type from valueT in struct case

In a struct case in type assertion, if the source is a valueT, we still need to take the struct type to allow method and field resolution.

* fix: handle all ptr structs as well
2020-07-06 15:09:48 +02:00
Nicholas Wiersma
a8b1c2a017 fix: a const, non-const equality check must convert 2020-07-06 11:25:03 +02:00
Nicholas Wiersma
d229c2a2c7 fix: handle Func Value in genFunctionWrapper params
* fix: handle Func value in func wrapper params

* fix: lint
2020-07-03 12:25:04 +02:00
Marc Vertes
2f2df7a0f8 fix: avoid memory errors by handling frame indirections (#739)
In all situations where the results are set directly
to the frame, and not using a value helper, the right level of
indirections must be applied, otherwise we may end-up writing
in the wrong frame (the local one, instead of a caller or global).

Fixes #735.
2020-07-03 11:02:46 +02:00
Nicholas Wiersma
4058fd8c44 fix: do type check on assignment (#738)
* fix: do type check on assignment

* fix: check for recursive type rather than field
2020-07-03 10:28:51 +02:00
Nicholas Wiersma
097a745e72 fix: variadic interface conversion in call/callBin 2020-07-02 23:55:03 +02:00
Nicholas Wiersma
1f514e63a8 fix: switch always compare like types 2020-07-02 23:35:03 +02:00
Nicholas Wiersma
a15ecb7176 feature: handle nested recursion
* fix: copy the type in recursion

In more advanced recursive cases, setting the rtype to interface may interfear with typeing. To stop this from happening, instead of setting t.val.rtype to interface in the hope it will be set correctly later, a copy if the type is made, and the rtype of the copy is set to interface{}.

* fix: detect intermediate recursive structs

In the case of a nested recussion, each symbol can have
a different perspective on the recursion. In this case,
it is impossible to move from one struct to the next.
To keep the perspectives the same, any intermediate struct
that contains a recursion should also be set to interface{}.
so that all perspectives are the same.

* fix: handle arb recursion

* chore: refactor dref to be consistent

* fix: invalid recursive struct issue

* fix: handle checkptr issue

* fix: move unsafe into function to stop ptr check

* fix: handle deref in assign
2020-07-02 23:20:03 +02:00
Marc Vertes
d4aa84f729 fix: set frame level in destination nodes to avoid memory corruption (#733)
When operations write their result to a non-local frame, the node
level field must be set accordingly, otherwise they attempt to write
in the wrong frame.

Fixes #730.
2020-07-02 10:03:32 +02:00
Marc Vertes
9977ef6fc6 Revert "fix: make interpreter methods discoverable by runtime (#722)" (#732)
This reverts commit a3b2737b5c.
2020-07-01 16:16:26 +02:00
Marc Vertes
39430c34bb fix: untyped constant converson to default type (#729)
* fix: untyped constant cconverson to default type

In definition assign expression, the source type is propagated to
the assigned value. If the source is an untyped constant, the
destination type must be set to the default type of the constant
definition. A fixType function is provided to perform this.

In addition, the type conversion and check of constants is
refactored for simplifications.

Fixes #727.

* test: fix _test/const14.go
2020-07-01 14:39:47 +02:00
mpl
4f3481b55c interp: support another vendoring case 2020-07-01 10:44:03 +02:00
Marc Vertes
55f2fe396a fix: goexports skisp unimplemented solaris Syscall6 (#726)
The standard library syscall package for Solaris defines unimplemented
symbols Syscall6 and RawSyscall6 which makes the build fails on
Solaris platform, now that yaegi command imports syscall symbols.

As the standard library package is locked down, this will remain
unchanged. We just skip those symbols.

Fixes #725.
2020-06-30 22:50:44 +02:00
Marc Vertes
108b6fd722 feature: add -syscall option to enable use of syscall symbols (#723) 2020-06-29 14:43:43 +02:00
Marc Vertes
a3b2737b5c fix: make interpreter methods discoverable by runtime (#722)
* fix: make interpreter methods discoverable by runtime

When generating an interface wrapper, lookup existing wrappers by method
to get the one with the biggest set of methods implemented by interpreter.

A string method is also added to wrappers, in order to provide a string
representation of the interpreter value rather than the wrapper itself
(at least for %s and %v verbs).

This allows the runtime to pickup an interpreter method automatically
even if the conversion to the interface is not specified in the script. As
in Go spec, it is enough for the type to implement the required methods.

A current limitation is that only single wrappers can be instantiated,
not allowing to compose interfaces.

This limitation can be removed when the Go reflect issue
https://github.com/golang/go/issues/15924 is fixed.

Fixes #435.

* test: add a simpler test
2020-06-29 14:25:14 +02:00
Nicholas Wiersma
d2c4a36c25 fix: dont optimize map index assigns 2020-06-29 09:40:03 +02:00
Nicholas Wiersma
f5f44f7ddd fix: rework compatibility and ordering checks 2020-06-25 09:44:04 +02:00
Nicholas Wiersma
4d013e4686 fix: handle defer in builtins 2020-06-25 09:28:03 +02:00
Julien Levesy
c11d361953 Handle Receive and Send channels 2020-06-23 09:04:04 +02:00
Nicholas Wiersma
c2ad279643 fix: use pragma for ptr checks 2020-06-22 16:40:03 +02:00
Nicholas Wiersma
9627782394 feature: add Sizeof and Alignof to unsafe 2020-06-22 15:24:04 +02:00
Nicholas Wiersma
e00b853971 fix: make reftype func variadic 2020-06-22 13:38:04 +02:00
mpl
7cfa264dbc interp: force root scope for binPkgT selector 2020-06-22 13:24:03 +02:00
Ludovic Fernandez
a6c24a0d13 chore: update linter. (#706)
* chore: update linter.

* chore: remove not needed travis env var.
2020-06-22 12:55:42 +02:00
Nicholas Wiersma
f19b7563ea feature: unsafe type conversion 2020-06-18 18:14:03 +02:00
Nicholas Wiersma
0643762852 fix: allow uint in make len and cap 2020-06-18 15:18:04 +02:00
mpl
7323d97023 interp: global scope redeclaration detection 2020-06-18 15:08:04 +02:00
Julien Levesy
6486909921 fix: range over channels returned by binary calls 2020-06-18 14:22:03 +02:00
Nicholas Wiersma
d252821df3 feature: constant values in bin packages 2020-06-18 13:54:04 +02:00
Nicholas Wiersma
2bef03e253 fix: import non-existant package 2020-06-18 09:44:03 +02:00
Nicholas Wiersma
1fe91be882 feature: refactor numeric constants and detect overflow 2020-06-18 09:20:04 +02:00
Nicholas Wiersma
5cbbf9339c fix: type assertion of non-matching types 2020-06-18 09:06:03 +02:00
274 changed files with 126700 additions and 123101 deletions

View File

@@ -38,6 +38,9 @@
"gocognit",
"stylecheck",
"gomnd",
"testpackage",
"goerr113",
"nestif",
]
[issues]

View File

@@ -26,7 +26,6 @@ matrix:
env:
global:
- GO111MODULE=on
- CI=1
go_import_path: github.com/containous/yaegi

19
_test/assign13.go Normal file
View File

@@ -0,0 +1,19 @@
package main
import "fmt"
func getStr() string {
return "test"
}
func main() {
m := make(map[string]string, 0)
m["a"] = fmt.Sprintf("%v", 0.1)
m["b"] = string(fmt.Sprintf("%v", 0.1))
m["c"] = getStr()
fmt.Println(m)
}
// Output:
// map[a:0.1 b:0.1 c:test]

16
_test/assign14.go Normal file
View File

@@ -0,0 +1,16 @@
package main
var optionsG map[string]string = nil
var roundG = 30
func main() {
dummy := roundG
roundG = dummy + 1
println(roundG)
println(optionsG == nil)
}
// Output:
// 31
// true

11
_test/assign15.go Normal file
View File

@@ -0,0 +1,11 @@
package main
func main() {
var c chan<- struct{} = make(chan struct{})
var d <-chan struct{} = c
_ = d
}
// Error:
// _test/assign15.go:5:26: cannot use type chan<- struct{} as type <-chan struct{} in assignment

12
_test/chan10.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import "time"
func main() {
var tick <-chan time.Time = time.Tick(time.Millisecond)
_ = tick
println("success")
}
// Output:
// success

14
_test/comp2.go Normal file
View File

@@ -0,0 +1,14 @@
package main
type delta int32
func main() {
a := delta(-1)
println(a != -1)
println(a == -1)
}
// Output:
// false
// true

15
_test/composite11.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import (
"fmt"
"image/color"
)
func main() {
c := color.NRGBA64{1, 1, 1, 1}
fmt.Println(c)
}
// Output:
// {1 1 1 1}

16
_test/const13.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"fmt"
)
func main() {
const tooBig = 1267650600228229401496703205376
const huge = 1 << 100
const large = huge >> 38
fmt.Println(large)
}
// Output:
// 4611686018427387904

13
_test/const14.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import "compress/flate"
func f1(i int) { println("i:", i) }
func main() {
i := flate.BestSpeed
f1(i)
}
// Output:
// i: 1

17
_test/const15.go Normal file
View File

@@ -0,0 +1,17 @@
package main
type T1 t1
type t1 int8
const (
P2 T1 = 2
P3 T1 = 3
)
func main() {
println(P3)
}
// Output:
// 3

32
_test/defer5.go Normal file
View File

@@ -0,0 +1,32 @@
package main
func f1() {
defer println("f1-begin")
f2()
defer println("f1-end")
}
func f2() {
defer println("f2-begin")
f3()
defer println("f2-end")
}
func f3() {
defer println("f3-begin")
println("hello")
defer println("f3-end")
}
func main() {
f1()
}
// Output:
// hello
// f3-end
// f3-begin
// f2-end
// f2-begin
// f1-end
// f1-begin

27
_test/defer6.go Normal file
View File

@@ -0,0 +1,27 @@
package main
func f1() {
defer print("f1-begin ")
f2()
defer print("f1-end ")
}
func f2() {
defer print("f2-begin ")
f3()
defer print("f2-end ")
}
func f3() {
defer print("f3-begin ")
print("hello ")
defer print("f3-end ")
}
func main() {
f1()
println()
}
// Output:
// hello f3-end f3-begin f2-end f2-begin f1-end f1-begin

18
_test/defer7.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import "fmt"
func f1(in, out []string) {
defer copy(out, in)
}
func main() {
in := []string{"foo", "bar"}
out := make([]string, 2)
f1(in, out)
fmt.Println(out)
}
// Output:
// [foo bar]

24
_test/defer8.go Normal file
View File

@@ -0,0 +1,24 @@
package main
import "fmt"
func f1(m map[string]string) {
defer delete(m, "foo")
defer delete(m, "test")
fmt.Println(m)
}
func main() {
m := map[string]string{
"foo": "bar",
"baz": "bat",
}
f1(m)
fmt.Println(m)
}
// Output:
// map[baz:bat foo:bar]
// map[baz:bat]

21
_test/defer9.go Normal file
View File

@@ -0,0 +1,21 @@
package main
import "fmt"
func f1(ch chan string) {
defer close(ch)
ch <- "foo"
}
func main() {
ch := make(chan string, 1)
f1(ch)
for s := range ch {
fmt.Println(s)
}
}
// Output:
// foo

12
_test/init1.go Normal file
View File

@@ -0,0 +1,12 @@
package main
func init() {
println("here")
}
func main() {
init()
}
// Error:
// _test/init1.go:8:2: undefined: init

19
_test/interface44.go Normal file
View File

@@ -0,0 +1,19 @@
package main
type S struct {
a int
}
func main() {
var i interface{} = S{a: 1}
s, ok := i.(S)
if !ok {
println("bad")
return
}
println(s.a)
}
// Output:
// 1

13
_test/interface45.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import "fmt"
func main() {
var i interface{} = 1
var s struct{}
s, _ = i.(struct{})
fmt.Println(s)
}
// Output:
// {}

29
_test/issue-735.go Normal file
View File

@@ -0,0 +1,29 @@
package main
import (
"fmt"
"strconv"
)
var optionsG map[string]string
var roundG int = 30
func strToInt(s string, defaultValue int) int {
n, err := strconv.ParseInt(s, 10, 0)
if err != nil {
return defaultValue
}
return int(n)
}
func main() {
optionsG := map[string]string{"round": "12", "b": "one"}
roundG = strToInt(optionsG["round"], 50)
fmt.Println(roundG)
fmt.Println(optionsG)
}
// Output:
// 12
// map[b:one round:12]

31
_test/issue-772.go Normal file
View File

@@ -0,0 +1,31 @@
package main
import (
"log"
"os"
"text/template"
)
type Message struct {
Data string
}
func main() {
tmpl := template.New("name")
_, err := tmpl.Parse("{{.Data}}")
if err != nil {
log.Fatal(err)
}
err = tmpl.Execute(os.Stdout, Message{
Data: "Hello, World!!",
})
if err != nil {
log.Fatal(err)
}
}
// Output:
// Hello, World!!

18
_test/issue-775.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"net/http/httptest"
)
func main() {
recorder := httptest.NewRecorder()
recorder.Header().Add("Foo", "Bar")
for key, value := range recorder.Header() {
fmt.Println(key, value)
}
}
// Output:
// Foo [Bar]

39
_test/issue-776.go Normal file
View File

@@ -0,0 +1,39 @@
package main
import "fmt"
// Filter is a filter
type Filter interface {
Foo()
}
// GIFT is a gift
type GIFT struct {
Filters []Filter
}
// New is a new filter list
func New(filters ...Filter) *GIFT {
return &GIFT{
Filters: filters,
}
}
// List lists filters
func (g *GIFT) List() {
fmt.Printf("Hello from List!\n")
}
// MyFilter is one of the filters
type MyFilter struct{}
// Foo is a foo
func (f *MyFilter) Foo() {}
func main() {
g := New(&MyFilter{})
g.List()
}
// Output:
// Hello from List!

12
_test/make2.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import "fmt"
func main() {
var s uint = 4
t := make([]int, s)
fmt.Println(t)
}
// Output:
// [0 0 0 0]

12
_test/math2.go Normal file
View File

@@ -0,0 +1,12 @@
package main
const c uint64 = 2
func main() {
if c&(1<<(uint64(1))) > 0 {
println("yes")
}
}
// Output:
// yes

17
_test/op6.go Normal file
View File

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

17
_test/op7.go Normal file
View File

@@ -0,0 +1,17 @@
package main
type T int
func (t T) Error() string { return "T: error" }
var invalidT T
func main() {
var err error
if err > invalidT {
println("ok")
}
}
// Error:
// _test/op7.go:11:5: invalid operation: operator > not defined on error

21
_test/op8.go Normal file
View File

@@ -0,0 +1,21 @@
package main
type I interface {
Get() interface{}
}
type T struct{}
func (T) Get() interface{} {
return nil
}
func main() {
var i I = T{}
var ei interface{}
println(i != ei)
}
// Output:
// true

11
_test/op9.go Normal file
View File

@@ -0,0 +1,11 @@
package main
func main() {
var i complex128 = 1i
var f complex128 = 0.4i
print(i > f)
}
// Error:
// _test/op9.go:7:8: invalid operation: operator > not defined on complex128

16
_test/range8.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"fmt"
"time"
)
func main() {
for _ = range time.Tick(time.Millisecond) {
fmt.Println("success")
return
}
}
// Output:
// success

11
_test/range9.go Normal file
View File

@@ -0,0 +1,11 @@
package main
func main() {
var c chan<- struct{} = make(chan struct{})
for _ = range c {
}
}
// Error:
// _test/range9.go:6:16: invalid operation: range c receive from send-only channel

View File

@@ -0,0 +1,14 @@
package main
type time int
var time string
func main() {
time = "hello"
println(time)
}
// Error:
// ../_test/redeclaration-global0.go:5:5: time redeclared in this block
// previous declaration at ../_test/redeclaration-global0.go:3:6

View File

@@ -0,0 +1,12 @@
package main
var time int
type time string
func main() {
var t time = "hello"
println(t)
}
// TODO: expected redeclaration error.

View File

@@ -0,0 +1,14 @@
package main
import (
"time"
"time"
)
func main() {
var t time.Time
println(t.String())
}
// Error:
// ../_test/redeclaration-global2.go:5:2: time/redeclaration-global2.go redeclared in this block

View File

@@ -0,0 +1,15 @@
package main
import (
"time"
)
var time string
func main() {
time = "hello"
println(t)
}
// Error:
// ../_test/redeclaration-global3.go:7:5: time redeclared in this block

View File

@@ -0,0 +1,15 @@
package main
import (
"time"
)
type time string
func main() {
var t time = "hello"
println(t)
}
// Error:
// ../_test/redeclaration-global4.go:7:6: time redeclared in this block

View File

@@ -0,0 +1,15 @@
package main
var time int
func time() string {
return "hello"
}
func main() {
t := time()
println(t)
}
// Error:
// ../_test/redeclaration-global5.go:5:1: time redeclared in this block

View File

@@ -0,0 +1,17 @@
package main
import (
"time"
)
func time() string {
return "hello"
}
func main() {
t := time()
println(t)
}
// Error:
// ../_test/redeclaration-global6.go:7:1: time redeclared in this block

18
_test/restricted0.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"log"
)
func main() {
defer func() {
r := recover()
fmt.Println("recover:", r)
}()
log.Fatal("log.Fatal does not exit")
println("not printed")
}
// Output:
// recover: log.Fatal does not exit

18
_test/restricted1.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"os"
)
func main() {
defer func() {
r := recover()
fmt.Println("recover:", r)
}()
os.Exit(1)
println("not printed")
}
// Output:
// recover: os.Exit(1)

14
_test/restricted2.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import (
"fmt"
"os"
)
func main() {
p, err := os.FindProcess(os.Getpid())
fmt.Println(p, err)
}
// Output:
// <nil> restricted

23
_test/restricted3.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
"bytes"
"fmt"
"log"
)
var (
buf bytes.Buffer
logger = log.New(&buf, "logger: ", log.Lshortfile)
)
func main() {
defer func() {
r := recover()
fmt.Println("recover:", r, buf.String())
}()
logger.Fatal("test log")
}
// Output:
// recover: test log logger: restricted.go:39: test log

18
_test/selector-scope0.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"time"
)
func test(time string, t time.Time) string {
return time
}
func main() {
str := test("test", time.Now())
fmt.Println(str)
}
// Output:
// test

View File

@@ -1,15 +1,23 @@
package main
import (
"encoding/json"
"os"
)
type S struct {
Child []*S
Name string
Child []*S
}
func main() {
s := &S{Name: "root"}
s.Child = append(s.Child, &S{Name: "child"})
println(s.Child[0].Name)
a := S{Name: "hello"}
a.Child = append(a.Child, &S{Name: "world"})
json.NewEncoder(os.Stdout).Encode(a)
a.Child[0].Child = append([]*S{}, &S{Name: "sunshine"})
json.NewEncoder(os.Stdout).Encode(a)
}
// Output:
// child
// {"Name":"hello","Child":[{"Name":"world","Child":null}]}
// {"Name":"hello","Child":[{"Name":"world","Child":[{"Name":"sunshine","Child":null}]}]}

33
_test/struct49.go Normal file
View File

@@ -0,0 +1,33 @@
package main
type S struct {
ts map[string][]*T
}
type T struct {
s *S
}
func (c *S) getT(addr string) (t *T, ok bool) {
cns, ok := c.ts[addr]
if !ok || len(cns) == 0 {
return nil, false
}
t = cns[len(cns)-1]
c.ts[addr] = cns[:len(cns)-1]
return t, true
}
func main() {
s := &S{
ts: map[string][]*T{},
}
s.ts["test"] = append(s.ts["test"], &T{s: s})
t , ok:= s.getT("test")
println(t != nil, ok)
}
// Output:
// true true

20
_test/struct50.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import "fmt"
type Node struct {
Name string
Child []Node
}
func main() {
a := Node{Name: "hello"}
a.Child = append([]Node{}, Node{Name: "world"})
fmt.Println(a)
a.Child[0].Child = append([]Node{}, Node{Name: "sunshine"})
fmt.Println(a)
}
// Output:
// {hello [{world []}]}
// {hello [{world [{sunshine []}]}]}

23
_test/struct51.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
"encoding/json"
"os"
)
type Node struct {
Name string
Child [2]*Node
}
func main() {
a := Node{Name: "hello"}
a.Child[0] = &Node{Name: "world"}
json.NewEncoder(os.Stdout).Encode(a)
a.Child[0].Child[0] = &Node{Name: "sunshine"}
json.NewEncoder(os.Stdout).Encode(a)
}
// Output:
// {"Name":"hello","Child":[{"Name":"world","Child":[null,null]},null]}
// {"Name":"hello","Child":[{"Name":"world","Child":[{"Name":"sunshine","Child":[null,null]},null]},null]}

20
_test/struct52.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import "fmt"
type Node struct {
Name string
Child map[string]Node
}
func main() {
a := Node{Name: "hello", Child: map[string]Node{}}
a.Child["1"] = Node{Name: "world", Child: map[string]Node{}}
fmt.Println(a)
a.Child["1"].Child["1"] = Node{Name: "sunshine", Child: map[string]Node{}}
fmt.Println(a)
}
// Output:
// {hello map[1:{world map[]}]}
// {hello map[1:{world map[1:{sunshine map[]}]}]}

23
_test/struct53.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import "fmt"
type T1 struct {
P []*T
}
type T2 struct {
P2 *T
}
type T struct {
*T1
S1 *T
}
func main() {
fmt.Println(T2{})
}
// Output:
// {<nil>}

26
_test/struct54.go Normal file
View File

@@ -0,0 +1,26 @@
package main
type S struct {
t *T
}
func newS() *S {
return &S{
t: &T{u: map[string]*U{}},
}
}
type T struct {
u map[string]*U
}
type U struct {
a int
}
func main() {
s := newS()
_ = s
println("ok")
}

28
_test/switch38.go Normal file
View File

@@ -0,0 +1,28 @@
package main
func isSeparator(c byte) bool {
switch c {
case '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t':
return true
}
return false
}
func main() {
s := "max-age=20"
for _, c := range []byte(s) {
println(string(c), isSeparator(c))
}
}
// Output:
// m false
// a false
// x false
// - false
// a false
// g false
// e false
// = true
// 2 false
// 0 false

18
_test/time13.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"time"
)
var dummy = 1
var t time.Time = time.Date(2007, time.November, 10, 23, 4, 5, 0, time.UTC)
func main() {
t = time.Date(2009, time.November, 10, 23, 4, 5, 0, time.UTC)
fmt.Println(t.Clock())
}
// Output:
// 23 4 5

38
_test/type23.go Normal file
View File

@@ -0,0 +1,38 @@
package main
import (
"net/http"
"net/http/httptest"
)
func main() {
var v1 interface{} = 1
var v2 interface{}
var v3 http.ResponseWriter = httptest.NewRecorder()
if r1, ok := v1.(string); ok {
_ = r1
println("unexpected")
}
if _, ok := v1.(string); ok {
println("unexpected")
}
if r2, ok := v2.(string); ok {
_ = r2
println("unexpected")
}
if _, ok := v2.(string); ok {
println("unexpected")
}
if r3, ok := v3.(http.Pusher); ok {
_ = r3
println("unexpected")
}
if _, ok := v3.(http.Pusher); ok {
println("unexpected")
}
println("bye")
}
// Output:
// bye

48
_test/type24.go Normal file
View File

@@ -0,0 +1,48 @@
package main
import (
"fmt"
"net/http"
"net/http/httptest"
)
func main() {
assertInt()
assertNil()
assertValue()
}
func assertInt() {
defer func() {
r := recover()
fmt.Println(r)
}()
var v interface{} = 1
println(v.(string))
}
func assertNil() {
defer func() {
r := recover()
fmt.Println(r)
}()
var v interface{}
println(v.(string))
}
func assertValue() {
defer func() {
r := recover()
fmt.Println(r)
}()
var v http.ResponseWriter = httptest.NewRecorder()
println(v.(http.Pusher))
}
// Output:
// interface conversion: interface {} is int, not string
// interface conversion: interface {} is nil, not string
// interface conversion: *httptest.ResponseRecorder is not http.Pusher: missing method Push

44
_test/type25.go Normal file
View File

@@ -0,0 +1,44 @@
package main
import (
"errors"
"sync/atomic"
)
type wrappedError struct {
wrapped error
}
func (e wrappedError) Error() string {
return "some outer error"
}
func (e wrappedError) Unwrap() error {
return e.wrapped
}
var err atomic.Value
func getWrapped() *wrappedError {
if v := err.Load(); v != nil {
err := v.(wrappedError)
if err.wrapped != nil {
return &err
}
}
return nil
}
func main() {
err.Store(wrappedError{wrapped: errors.New("test")})
e := getWrapped()
if e != nil {
println(e.Error())
println(e.wrapped.Error())
}
}
// Output:
// some outer error
// test

44
_test/type26.go Normal file
View File

@@ -0,0 +1,44 @@
package main
import (
"errors"
"sync/atomic"
)
type wrappedError struct {
wrapped error
}
func (e *wrappedError) Error() string {
return "some outer error"
}
func (e *wrappedError) Unwrap() error {
return e.wrapped
}
var err atomic.Value
func getWrapped() *wrappedError {
if v := err.Load(); v != nil {
err := v.(*wrappedError)
if err.wrapped != nil {
return err
}
}
return nil
}
func main() {
err.Store(&wrappedError{wrapped: errors.New("test")})
e := getWrapped()
if e != nil {
println(e.Error())
println(e.wrapped.Error())
}
}
// Output:
// some outer error
// test

15
_test/unsafe0.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import "unsafe"
func main() {
str := "foobar"
p := unsafe.Pointer(&str)
str2 := *(*string)(p)
println(str2)
}
// Output:
// foobar

20
_test/unsafe1.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import "unsafe"
type S struct {
Name string
}
func main() {
s := &S{Name: "foobar"}
p := unsafe.Pointer(s)
s2 := (*S)(p)
println(s2.Name)
}
// Output:
// foobar

20
_test/unsafe2.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import (
"fmt"
"unsafe"
)
func main() {
str := "foobar"
ptr := unsafe.Pointer(&str)
p := uintptr(ptr)
s1 := fmt.Sprintf("%x", ptr)
s2 := fmt.Sprintf("%x", p)
println(s1 == s2)
}
// Output:
// true

26
_test/unsafe3.go Normal file
View File

@@ -0,0 +1,26 @@
package main
import (
"fmt"
"unsafe"
)
const SSize = 16
type S struct {
X int
Y int
}
func main() {
var sBuf [SSize]byte
s := (*S)(unsafe.Pointer(&sBuf[0]))
s.X = 2
s.Y = 4
fmt.Println(sBuf)
}
// Output:
// [2 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0]

29
_test/unsafe4.go Normal file
View File

@@ -0,0 +1,29 @@
package main
import (
"fmt"
"unsafe"
)
const SSize = 24
type S struct {
X int
Y int
Z int
}
func main() {
arr := []S{
{X: 1},
{X: 2},
{X: 3},
}
addr := unsafe.Pointer(&arr[0])
s := *(*S)(unsafe.Pointer(uintptr(addr) + SSize*2))
fmt.Println(s.X)
}
// Output:
// 3

22
_test/unsafe5.go Normal file
View File

@@ -0,0 +1,22 @@
package main
import (
"fmt"
"unsafe"
)
type S struct {
X int
Y int
Z int
}
func main() {
size := unsafe.Sizeof(S{})
align := unsafe.Alignof(S{})
fmt.Println(size, align)
}
// Output:
// 24 8

13
_test/var12.go Normal file
View File

@@ -0,0 +1,13 @@
package main
var (
a = b
b = "hello"
)
func main() {
println(a)
}
// Output:
// hello

23
_test/var13.go Normal file
View File

@@ -0,0 +1,23 @@
package main
var (
a = concat("hello", b)
b = concat(" ", c, "!")
c = d
d = "world"
)
func concat(a ...string) string {
var s string
for _, ss := range a {
s += ss
}
return s
}
func main() {
println(a)
}
// Output:
// hello world!

10
_test/var14.go Normal file
View File

@@ -0,0 +1,10 @@
package main
import "github.com/containous/yaegi/_test/vars"
func main() {
println(vars.A)
}
// Output:
// hello world!

17
_test/variadic8.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import (
"fmt"
"time"
)
func main() {
fn1 := func(args ...*time.Duration) string {
return ""
}
fmt.Printf("%T\n", fn1)
}
// Output:
// func(...*time.Duration) string

14
_test/variadic9.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
func Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, a...)
}
func main() {
fmt.Println(Sprintf("Hello %s", "World!"))
}
// Output:
// Hello World!

14
_test/vars/first.go Normal file
View File

@@ -0,0 +1,14 @@
package vars
var (
A = concat("hello", B)
C = D
)
func concat(a ...string) string {
var s string
for _, ss := range a {
s += ss
}
return s
}

6
_test/vars/second.go Normal file
View File

@@ -0,0 +1,6 @@
package vars
var (
B = concat(" ", C, "!")
D = "world"
)

View File

@@ -30,6 +30,7 @@ import (
"go/types"
"io/ioutil"
"log"
"math/big"
"os"
"path"
"runtime"
@@ -97,24 +98,35 @@ func init() {
{{end}}
`
// Val store the value name and addressable status of symbols
// Val store the value name and addressable status of symbols.
type Val struct {
Name string // "package.name"
Addr bool // true if symbol is a Var
}
// Method store information for generating interface wrapper method
// Method store information for generating interface wrapper method.
type Method struct {
Name, Param, Result, Arg, Ret string
}
// Wrap store information for generating interface wrapper
// Wrap store information for generating interface wrapper.
type Wrap struct {
Name string
Method []Method
}
func genContent(dest, pkgName, license string) ([]byte, error) {
// restricted map defines symbols for which a special implementation is provided.
var restricted = map[string]bool{
"osExit": true,
"osFindProcess": true,
"logFatal": true,
"logFatalf": true,
"logFatalln": true,
"logLogger": true,
"logNew": true,
}
func genContent(dest, pkgName, license string, skip map[string]bool) ([]byte, error) {
p, err := importer.ForCompiler(token.NewFileSet(), "source", nil).Import(pkgName)
if err != nil {
return nil, err
@@ -146,11 +158,19 @@ func genContent(dest, pkgName, license string) ([]byte, error) {
}
pname := path.Base(pkgName) + "." + name
if skip[pname] {
continue
}
if rname := path.Base(pkgName) + name; restricted[rname] {
// Restricted symbol, locally provided by stdlib wrapper.
pname = rname
}
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
val[name] = Val{fixConst(pname, o.Val()), false}
val[name] = Val{fixConst(pname, o.Val(), imports), false}
} else {
val[name] = Val{pname, false}
}
@@ -252,36 +272,36 @@ func genContent(dest, pkgName, license string) ([]byte, error) {
return source, nil
}
// fixConst checks untyped constant value, converting it if necessary to avoid overflow
func fixConst(name string, val constant.Value) string {
// fixConst checks untyped constant value, converting it if necessary to avoid overflow.
func fixConst(name string, val constant.Value, imports map[string]bool) string {
var (
tok string
str string
)
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()
i, err := strconv.ParseInt(str, 0, 64)
if err == nil {
switch {
case i == int64(int32(i)):
return name
case i == int64(uint32(i)):
return "uint32(" + name + ")"
default:
return "int64(" + name + ")"
}
tok = "INT"
str = val.ExactString()
case constant.Float:
v := constant.Val(val) // v is *big.Rat or *big.Float
f, ok := v.(*big.Float)
if !ok {
f = new(big.Float).SetRat(v.(*big.Rat))
}
_, err = strconv.ParseUint(str, 0, 64)
if err == nil {
return "uint64(" + name + ")"
}
return name
tok = "FLOAT"
str = f.Text('g', int(f.Prec()))
case constant.Complex:
// TODO: not sure how to parse this case
fallthrough
default:
return name
}
imports["go/constant"] = true
imports["go/token"] = true
return fmt.Sprintf("constant.MakeFromLiteral(\"%s\", token.%s, 0)", str, tok)
}
// genLicense generates the correct LICENSE header text from the provided
@@ -335,20 +355,25 @@ func main() {
dest := path.Base(dir)
for _, pkg := range flag.Args() {
content, err := genContent(dest, pkg, license)
if err != nil {
log.Println(err)
continue
}
var oFile string
skip := map[string]bool{}
if pkg == "syscall" {
goos, arch := os.Getenv("GOOS"), os.Getenv("GOARCH")
oFile = strings.Replace(pkg, "/", "_", -1) + "_" + goos + "_" + arch + ".go"
if goos == "solaris" {
skip["syscall.RawSyscall6"] = true
skip["syscall.Syscall6"] = true
}
} else {
oFile = strings.Replace(pkg, "/", "_", -1) + ".go"
}
content, err := genContent(dest, pkg, license, skip)
if err != nil {
log.Println(err)
continue
}
prefix := runtime.Version()
if runtime.Version() != "devel" {
parts := strings.Split(runtime.Version(), ".")

View File

@@ -63,9 +63,13 @@ Options:
evaluate the string and return.
-i
start an interactive REPL after file execution.
-syscall
include syscall symbols.
-tags tag,list
a comma-separated list of build tags to consider satisfied during
the interpretation.
-unsafe
include unsafe symbols.
Debugging support (may be removed at any time):
YAEGI_AST_DOT=1
@@ -91,14 +95,23 @@ import (
"github.com/containous/yaegi/interp"
"github.com/containous/yaegi/stdlib"
"github.com/containous/yaegi/stdlib/syscall"
"github.com/containous/yaegi/stdlib/unrestricted"
"github.com/containous/yaegi/stdlib/unsafe"
)
func main() {
var interactive bool
var useSyscall bool
var useUnrestricted bool
var useUnsafe bool
var tags string
var cmd string
flag.BoolVar(&interactive, "i", false, "start an interactive REPL")
flag.BoolVar(&useSyscall, "syscall", false, "include syscall symbols")
flag.BoolVar(&useUnrestricted, "unrestricted", false, "include unrestricted symbols")
flag.StringVar(&tags, "tags", "", "set a list of build tags")
flag.BoolVar(&useUnsafe, "unsafe", false, "include usafe symbols")
flag.StringVar(&cmd, "e", "", "set the command to be executed (instead of script or/and shell)")
flag.Usage = func() {
fmt.Println("Usage:", os.Args[0], "[options] [script] [args]")
@@ -112,6 +125,16 @@ func main() {
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
i.Use(stdlib.Symbols)
i.Use(interp.Symbols)
if useSyscall {
i.Use(syscall.Symbols)
}
if useUnsafe {
i.Use(unsafe.Symbols)
}
if useUnrestricted {
// Use of unrestricted symbols should always follow use of stdlib symbols, to update them.
i.Use(unrestricted.Symbols)
}
if cmd != `` {
i.REPL(strings.NewReader(cmd), os.Stderr)

View File

@@ -0,0 +1,17 @@
package pkg
import (
"fmt"
"guthib.com/containous/fromage"
)
func Here() string {
return "root"
}
func NewSample() func() string {
return func() string {
return fmt.Sprintf("%s %s", Here(), fromage.Hello())
}
}

View File

@@ -0,0 +1,7 @@
package fromage
import "fmt"
func Hello() string {
return fmt.Sprint("Fromage")
}

View File

@@ -0,0 +1,11 @@
package main
import (
"fmt"
"guthib.com/containous/fromage"
)
func main() {
fmt.Print(fromage.Hello())
}

View File

@@ -0,0 +1,7 @@
package fromage
import "fmt"
func Hello() string {
return fmt.Sprint("Fromage")
}

View File

@@ -0,0 +1,11 @@
package main
import (
"fmt"
"guthib.com/foo/pkg"
)
func main() {
fmt.Printf("%s", pkg.NewSample()())
}

View File

@@ -0,0 +1,17 @@
package pkg
import (
"fmt"
"guthib.com/bar"
)
func Here() string {
return "hello"
}
func NewSample() func() string {
return func() string {
return fmt.Sprintf("%s %s", bar.Bar(), Here())
}
}

View File

@@ -0,0 +1,6 @@
package bar
// Bar is bar
func Bar() string {
return "Yo"
}

View File

@@ -1,6 +1,11 @@
package pkg
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"testing"
@@ -10,9 +15,11 @@ import (
func TestPackages(t *testing.T) {
testCases := []struct {
desc string
goPath string
expected string
desc string
goPath string
expected string
topImport string
evalFile string
}{
{
desc: "vendor",
@@ -64,6 +71,30 @@ func TestPackages(t *testing.T) {
goPath: "./_pkg8/",
expected: "root Fromage!",
},
{
desc: "at the project root",
goPath: "./_pkg10/",
expected: "root Fromage",
topImport: "github.com/foo",
},
{
desc: "eval main that has vendored dep",
goPath: "./_pkg11/",
expected: "Fromage",
evalFile: "./_pkg11/src/foo/foo.go",
},
{
desc: "vendor dir is a sibling or an uncle",
goPath: "./_pkg12/",
expected: "Yo hello",
topImport: "guthib.com/foo/pkg",
},
{
desc: "eval main with vendor as a sibling",
goPath: "./_pkg12/",
expected: "Yo hello",
evalFile: "./_pkg12/src/guthib.com/foo/main.go",
},
}
for _, test := range testCases {
@@ -78,27 +109,73 @@ func TestPackages(t *testing.T) {
i := interp.New(interp.Options{GoPath: goPath})
i.Use(stdlib.Symbols) // Use binary standard library
// Load pkg from sources
if _, err = i.Eval(`import "github.com/foo/pkg"`); err != nil {
t.Fatal(err)
var msg string
if test.evalFile != "" {
// setting i.Name as this is how it's actually done in cmd/yaegi
i.Name = test.evalFile
data, err := ioutil.ReadFile(test.evalFile)
if err != nil {
t.Fatal(err)
}
// TODO(mpl): this is brittle if we do concurrent tests and stuff, do better later.
stdout := os.Stdout
defer func() { os.Stdout = stdout }()
pr, pw, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
os.Stdout = pw
if _, err := i.Eval(string(data)); err != nil {
fatalStderrf(t, "%v", err)
}
var buf bytes.Buffer
errC := make(chan error)
go func() {
_, err := io.Copy(&buf, pr)
errC <- err
}()
if err := pw.Close(); err != nil {
fatalStderrf(t, "%v", err)
}
if err := <-errC; err != nil {
fatalStderrf(t, "%v", err)
}
msg = buf.String()
} else {
// Load pkg from sources
topImport := "github.com/foo/pkg"
if test.topImport != "" {
topImport = test.topImport
}
if _, err = i.Eval(fmt.Sprintf(`import "%s"`, topImport)); err != nil {
t.Fatal(err)
}
value, err := i.Eval(`pkg.NewSample()`)
if err != nil {
t.Fatal(err)
}
fn := value.Interface().(func() string)
msg = fn()
}
value, err := i.Eval(`pkg.NewSample()`)
if err != nil {
t.Fatal(err)
}
fn := value.Interface().(func() string)
msg := fn()
if msg != test.expected {
t.Errorf("Got %q, want %q", msg, test.expected)
fatalStderrf(t, "Got %q, want %q", msg, test.expected)
}
})
}
}
func fatalStderrf(t *testing.T, format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
t.FailNow()
}
func TestPackagesError(t *testing.T) {
testCases := []struct {
desc string

View File

@@ -5,6 +5,7 @@ import (
"go/format"
"io/ioutil"
"log"
"strings"
"text/template"
)
@@ -12,7 +13,11 @@ const model = `package interp
// Code generated by 'go run ../internal/genop/genop.go'. DO NOT EDIT.
import "reflect"
import (
"go/constant"
"go/token"
"reflect"
)
// Arithmetic operators
{{range $name, $op := .Arithmetic}}
@@ -176,9 +181,22 @@ func {{$name}}(n *node) {
func {{$name}}Const(n *node) {
v0, v1 := n.child[0].rval, n.child[1].rval
isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type()))
t := n.typ.rtype
if isConst {
t = constVal
}
n.rval = reflect.New(t).Elem()
switch {
case isConst:
{{- if $op.Shift}}
s, _ := constant.Uint64Val(vConstantValue(v1))
v := constant.Shift(vConstantValue(v0), token.{{tokenFromName $name}}, uint(s))
n.rval.Set(reflect.ValueOf(v))
{{- else}}
v := constant.BinaryOp(vConstantValue(v0), token.{{tokenFromName $name}}, vConstantValue(v1))
n.rval.Set(reflect.ValueOf(v))
{{- end}}
{{- if $op.Str}}
case isString(t):
n.rval.SetString(v0.String() {{$op.Name}} v1.String())
@@ -354,23 +372,35 @@ func {{$name}}(n *node) {
{{end}}
{{range $name, $op := .Unary}}
func {{$name}}Const(n *node) {
v0 := n.child[0].rval
isConst := v0.IsValid() && isConstantValue(v0.Type())
t := n.typ.rtype
v := n.child[0].rval
if isConst {
t = constVal
}
n.rval = reflect.New(t).Elem()
{{- if $op.Bool}}
n.rval.SetBool({{$op.Name}} v.Bool())
if isConst {
v := constant.UnaryOp(token.{{tokenFromName $name}}, vConstantValue(v0), 0)
n.rval.Set(reflect.ValueOf(v))
} else {
n.rval.SetBool({{$op.Name}} v0.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())
switch {
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())
{{- 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())
case isFloat(t):
n.rval.SetFloat({{$op.Name}} v0.Float())
case isComplex(t):
n.rval.SetComplex({{$op.Name}} v0.Complex())
{{- end}}
}
{{- end}}
@@ -382,6 +412,83 @@ func {{$name}}(n *node) {
dest := genValueOutput(n, reflect.TypeOf(true))
c0, c1 := n.child[0], n.child[1]
{{- if or (eq $op.Name "==") (eq $op.Name "!=") }}
if c0.typ.cat == aliasT || c1.typ.cat == aliasT {
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 != 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 != 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 != 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
}
}
}
return
}
{{- end}}
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
case isString(t0) || isString(t1):
switch {
@@ -810,7 +917,7 @@ func {{$name}}(n *node) {
{{end}}
`
// Op define operator name and properties
// Op define operator name and properties.
type Op struct {
Name string // +, -, ...
Str bool // true if operator applies to string
@@ -822,6 +929,22 @@ type Op struct {
func main() {
base := template.New("goexports")
base.Funcs(template.FuncMap{
"tokenFromName": func(name string) string {
switch name {
case "andNot":
return "AND_NOT"
case "neg":
return "SUB"
case "pos":
return "ADD"
case "bitNot":
return "XOR"
default:
return strings.ToUpper(name)
}
},
})
parse, err := base.Parse(model)
if err != nil {
log.Fatal(err)

View File

@@ -3,6 +3,7 @@ package interp
import (
"fmt"
"go/ast"
"go/constant"
"go/parser"
"go/scanner"
"go/token"
@@ -11,10 +12,10 @@ import (
"sync/atomic"
)
// nkind defines the kind of AST, i.e. the grammar category
// nkind defines the kind of AST, i.e. the grammar category.
type nkind uint
// Node kinds for the go language
// Node kinds for the go language.
const (
undefNode nkind = iota
addressExpr
@@ -30,6 +31,8 @@ const (
caseBody
caseClause
chanType
chanTypeSend
chanTypeRecv
commClause
commClauseDefault
compositeLitExpr
@@ -108,6 +111,8 @@ var kinds = [...]string{
caseBody: "caseBody",
caseClause: "caseClause",
chanType: "chanType",
chanTypeSend: "chanTypeSend",
chanTypeRecv: "chanTypeRecv",
commClause: "commClause",
commClauseDefault: "commClauseDefault",
compositeLitExpr: "compositeLitExpr",
@@ -178,13 +183,13 @@ func (k nkind) String() string {
return "nKind(" + strconv.Itoa(int(k)) + ")"
}
// astError represents an error during AST build stage
// astError represents an error during AST build stage.
type astError error
// action defines the node action to perform at execution
// action defines the node action to perform at execution.
type action uint
// Node actions for the go language
// Node actions for the go language.
const (
aNop action = iota
aAddr
@@ -245,6 +250,7 @@ const (
aTypeAssert
aXor
aXorAssign
aMax
)
var actions = [...]string{
@@ -474,14 +480,14 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
v, _, _, _ := strconv.UnquoteChar(a.Value[1:len(a.Value)-1], '\'')
n.rval = reflect.ValueOf(v)
case token.FLOAT:
v, _ := strconv.ParseFloat(a.Value, 64)
v := constant.MakeFromLiteral(a.Value, a.Kind, 0)
n.rval = reflect.ValueOf(v)
case token.IMAG:
v, _ := strconv.ParseFloat(a.Value[:len(a.Value)-1], 64)
n.rval = reflect.ValueOf(complex(0, v))
v := constant.MakeFromLiteral(a.Value, a.Kind, 0)
n.rval = reflect.ValueOf(v)
case token.INT:
v, _ := strconv.ParseInt(a.Value, 0, 0)
n.rval = reflect.ValueOf(int(v))
v := constant.MakeFromLiteral(a.Value, a.Kind, 0)
n.rval = reflect.ValueOf(v)
case token.STRING:
v, _ := strconv.Unquote(a.Value)
n.rval = reflect.ValueOf(v)
@@ -564,7 +570,14 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
st.push(addChild(&root, anc, pos, caseClause, aCase), nod)
case *ast.ChanType:
st.push(addChild(&root, anc, pos, chanType, aNop), nod)
switch a.Dir {
case ast.SEND | ast.RECV:
st.push(addChild(&root, anc, pos, chanType, aNop), nod)
case ast.SEND:
st.push(addChild(&root, anc, pos, chanTypeSend, aNop), nod)
case ast.RECV:
st.push(addChild(&root, anc, pos, chanTypeRecv, aNop), nod)
}
case *ast.CommClause:
kind := commClause
@@ -869,7 +882,7 @@ func (s *nodestack) top() astNode {
return astNode{}
}
// dup returns a duplicated node subtree
// dup returns a duplicated node subtree.
func (interp *Interpreter) dup(nod, anc *node) *node {
nindex := atomic.AddInt64(&interp.nindex, 1)
n := *nod

View File

@@ -31,7 +31,7 @@ func (interp *Interpreter) buildOk(ctx *build.Context, name, src string) (bool,
}
// 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) {
if len(line) < 7 || line[:7] != "+build " {
return true
@@ -46,7 +46,7 @@ func buildLineOk(ctx *build.Context, line string) (ok bool) {
return ok
}
// 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 {
// in option, evaluate the AND of individual tags
for _, t := range strings.Split(tag, ",") {
@@ -58,7 +58,7 @@ func buildOptionOk(ctx *build.Context, tag string) bool {
}
// 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) {
not := s[0] == '!'
if not {
@@ -112,7 +112,7 @@ func contains(tags []string, tag string) bool {
return false
}
// goMinorVersion returns the go minor version number
// goMinorVersion returns the go minor version number.
func goMinorVersion(ctx *build.Context) int {
current := ctx.ReleaseTags[len(ctx.ReleaseTags)-1]
@@ -128,7 +128,7 @@ func goMinorVersion(ctx *build.Context) int {
return m
}
// skipFile returns true if file should be skipped
// skipFile returns true if file should be skipped.
func skipFile(ctx *build.Context, p string) bool {
if !strings.HasSuffix(p, ".go") {
return true

View File

@@ -2,14 +2,17 @@ package interp
import (
"fmt"
"go/constant"
"log"
"math"
"path/filepath"
"reflect"
"regexp"
"strings"
"unicode"
)
// A cfgError represents an error during CFG build stage
// A cfgError represents an error during CFG build stage.
type cfgError struct {
*node
error
@@ -46,12 +49,14 @@ var identifier = regexp.MustCompile(`([\pL_][\pL_\d]*)$`)
// cfg generates a control flow graph (CFG) from AST (wiring successors in AST)
// and pre-compute frame sizes and indexes for all un-named (temporary) and named
// variables. A list of nodes of init functions is returned.
// Following this pass, the CFG is ready to run
// Following this pass, the CFG is ready to run.
func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
sc := interp.initScopePkg(pkgID)
var initNodes []*node
var err error
baseName := filepath.Base(interp.fset.Position(root.pos).Filename)
root.Walk(func(n *node) bool {
// Pre-order processing
if err != nil {
@@ -65,6 +70,12 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
// type of array like value is not yet known. This could be fixed in ast structure
// by setting array/map node as 1st child of ForRangeStmt instead of 3rd child of
// RangeStmt. The following workaround is less elegant but ok.
c := n.anc.child[1]
if c != nil && c.typ != nil && isSendChan(c.typ) {
err = c.cfgErrorf("invalid operation: range %s receive from send-only channel", c.ident)
return false
}
if t := sc.rangeChanType(n.anc); t != nil {
// range over channel
e := n.anc.child[0]
@@ -398,7 +409,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
}
case arrayType, basicLit, chanType, funcType, interfaceType, mapType, structType:
case arrayType, basicLit, chanType, chanTypeRecv, chanTypeSend, funcType, interfaceType, mapType, structType:
n.typ, err = nodeType(interp, sc, n)
return false
}
@@ -459,9 +470,15 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
if src.typ.isBinMethod {
dest.typ = &itype{cat: valueT, rtype: src.typ.methodCallType()}
} else {
dest.typ = src.typ
// In a new definition, propagate the source type to the destination
// type. If the source is an untyped constant, make sure that the
// type matches a default type.
dest.typ = sc.fixType(src.typ)
}
}
if dest.typ.incomplete {
return
}
if dest.typ.sizedef {
dest.typ.size = arrayTypeLen(src)
dest.typ.rtype = nil
@@ -504,13 +521,30 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
err = src.cfgErrorf("invalid float truncate")
return
}
// TODO: Rudimentary type check at this point,
// improvements need to be made to make it better.
switch {
case dest.typ.untyped || src.typ.untyped:
// Both side of the assignment must be typed.
case isRecursiveType(dest.typ, dest.typ.rtype) || isRecursiveType(src.typ, src.typ.rtype):
// Recursive types cannot be type checked.
case t0.Kind() == reflect.Interface || t0.Kind() == reflect.Func:
// We have no way of checking interfaces and functions.
case t1.AssignableTo(t0):
// All is well when they are assignable.
default:
err = src.cfgErrorf("cannot use type %s as type %s in assignment", src.typ.id(), dest.typ.id())
return
}
}
n.findex = dest.findex
n.level = dest.level
// Propagate type
// TODO: Check that existing destination type matches source type
switch {
case n.action == aAssign && isCall(src) && dest.typ.cat != interfaceT && !isRecursiveField(dest):
case n.action == aAssign && isCall(src) && dest.typ.cat != interfaceT && !isMapEntry(dest) && !isRecursiveField(dest):
// Call action may perform the assignment directly.
n.gen = nop
src.level = level
@@ -543,8 +577,11 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
case !src.rval.IsValid():
// Assign to nil.
src.rval = reflect.New(dest.typ.TypeOf()).Elem()
case n.anc.kind == constDecl:
// Possible conversion from const to actual type will be handled later
default:
// Convert literal value to destination type.
convertConstantValue(src)
src.rval = src.rval.Convert(dest.typ.TypeOf())
src.typ = dest.typ
}
@@ -559,6 +596,12 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
dest.gen = nop // skip getIndexMap
}
if n.anc.kind == constDecl {
if !dest.typ.untyped {
// If the dest is untyped, any constant rval needs to be converted
convertConstantValue(src)
}
n.gen = nop
n.findex = -1
sc.sym[dest.ident].kind = constSym
if childPos(n) == len(n.anc.child)-1 {
sc.iota = 0
@@ -620,40 +663,91 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
nilSym := interp.universe.sym["nil"]
c0, c1 := n.child[0], n.child[1]
t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf()
// Shift operator type is inherited from first parameter only.
// All other binary operators require both parameter types to be the same.
if !isShiftNode(n) && !c0.typ.untyped && !c1.typ.untyped && !c0.typ.equals(c1.typ) {
err = n.cfgErrorf("mismatched types %s and %s", c0.typ.id(), c1.typ.id())
isConstVal := func(n *node) bool {
return n.rval.IsValid() && isConstantValue(n.rval.Type())
}
// Type check the binary expression. Mimics Gos logic as closely and possible.
c := c0
if isConstVal(c) {
c = c1
}
if isShiftNode(n) {
if !c1.isNatural() {
err = n.cfgErrorf("invalid operation: shift count type %v, must be integer", strings.TrimLeft(c1.typ.id(), "."))
break
}
if !c0.isInteger() {
err = n.cfgErrorf("invalid operation: shift of type %v", strings.TrimLeft(c0.typ.id(), "."))
break
}
}
if !isShiftNode(n) && isComparisonNode(n) && !isConstVal(c) && !c0.typ.equals(c1.typ) {
if isInterface(c1.typ) && !isInterface(c0.typ) && !c0.typ.comparable() {
err = n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, strings.TrimLeft(c0.typ.id(), "."))
break
}
if isInterface(c0.typ) && !isInterface(c1.typ) && !c1.typ.comparable() {
err = n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, strings.TrimLeft(c1.typ.id(), "."))
break
}
}
if !isShiftNode(n) && !isConstVal(c) && !c0.typ.equals(c1.typ) && t0 != nil && t1 != nil {
switch {
case isConstVal(c0) && isNumber(t1) || isConstVal(c1) && isNumber(t0): // const <-> numberic case
case isNumber(t0) && isNumber(t1) && (c0.typ.untyped || c1.typ.untyped):
case t0.Kind() == reflect.Uint8 && t1.Kind() == reflect.Int32 || t1.Kind() == reflect.Uint8 && t0.Kind() == reflect.Int32: // byte <-> rune case
case isInterface(c0.typ) && isInterface(c1.typ): // interface <-> interface case
default:
err = n.cfgErrorf("invalid operation: mismatched types %s and %s", strings.TrimLeft(c0.typ.id(), "."), strings.TrimLeft(c1.typ.id(), "."))
}
if err != nil {
break
}
}
cat := c.typ.cat
switch {
case isConstVal(c):
cat = catOfConst(c.rval)
case c.typ.cat == valueT:
cat = catOf(c.typ.rtype)
case c.typ.cat == aliasT:
cat = c.typ.val.cat
}
if !isShiftNode(n) && !okFor[n.action][cat] {
err = n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, strings.TrimLeft(c0.typ.id(), "."))
break
}
if !isShiftNode(n) && isConstVal(c0) && isConstVal(c1) {
// If both are constants, check the left type as well.
if !okFor[n.action][catOfConst(c0.rval)] {
err = n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, strings.TrimLeft(c0.typ.id(), "."))
break
}
}
switch n.action {
case aAdd:
if !(isNumber(t0) && isNumber(t1) || isString(t0) && isString(t1)) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
case aSub, aMul, aQuo:
if !(isNumber(t0) && isNumber(t1)) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
case aAnd, aOr, aXor, aAndNot:
if !(isInt(t0) && isInt(t1)) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
case aRem:
if !(c0.isInteger() && c1.isInteger()) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
n.typ = c0.typ
case aShl, aShr:
if !(c0.isInteger() && c1.isNatural()) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
case aRem, aShl, aShr:
n.typ = c0.typ
case aEqual, aNotEqual:
if isNumber(t0) && !isNumber(t1) || isString(t0) && !isString(t1) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
n.typ = sc.getType("bool")
if isConstVal(c0) && !isConstVal(c1) || !isConstVal(c0) && isConstVal(c1) {
// if either node is a constant value, but the other is not, the constant
// must be converted into the non-constants type.
switch {
case isConstVal(c0):
convertConstantValue(c0)
c0.rval = c0.rval.Convert(c1.typ.TypeOf())
default:
convertConstantValue(c1)
c1.rval = c1.rval.Convert(c0.typ.TypeOf())
}
}
if n.child[0].sym == nilSym || n.child[1].sym == nilSym {
if n.action == aEqual {
n.gen = isNil
@@ -662,9 +756,6 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
}
case aGreater, aGreaterEqual, aLower, aLowerEqual:
if isNumber(t0) && !isNumber(t1) || isString(t0) && !isString(t1) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
n.typ = sc.getType("bool")
}
if err != nil {
@@ -691,6 +782,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
dest := n.anc.child[childPos(n)-n.anc.nright]
n.typ = dest.typ
n.findex = dest.findex
n.level = dest.level
case n.anc.kind == returnStmt:
// To avoid a copy in frame, if the result is to be returned, store it directly
// at the frame location reserved for output arguments.
@@ -746,6 +838,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
if len(n.child) > 0 {
l := n.lastChild()
n.findex = l.findex
n.level = l.level
n.val = l.val
n.sym = l.sym
n.typ = l.typ
@@ -753,13 +846,22 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
sc = sc.pop()
case constDecl, varDecl:
case constDecl:
wireChild(n)
case varDecl:
// Global varDecl do not need to be wired as this
// will be handled after cfg.
if n.anc.kind == fileStmt {
break
}
wireChild(n)
case declStmt, exprStmt, sendStmt:
wireChild(n)
l := n.lastChild()
n.findex = l.findex
n.level = l.level
n.val = l.val
n.sym = l.sym
n.typ = l.typ
@@ -826,6 +928,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.gen = nop
n.typ = n.child[1].typ
n.findex = n.child[1].findex
n.level = n.child[1].level
n.val = n.child[1].val
n.rval = n.child[1].rval
} else {
@@ -939,7 +1042,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
case fileStmt:
wireChild(n)
wireChild(n, varDecl)
sc = sc.pop()
n.findex = -1
@@ -1051,11 +1154,11 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.types = sc.types
sc = sc.pop()
funcName := n.child[1].ident
if !isMethod(n) {
interp.scopes[pkgID].sym[funcName].index = -1 // to force value to n.val
interp.scopes[pkgID].sym[funcName].typ = n.typ
interp.scopes[pkgID].sym[funcName].kind = funcSym
interp.scopes[pkgID].sym[funcName].node = n
if sym := sc.sym[funcName]; !isMethod(n) && sym != nil {
sym.index = -1 // to force value to n.val
sym.typ = n.typ
sym.kind = funcSym
sym.node = n
}
case funcLit:
@@ -1070,36 +1173,48 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
if isKey(n) || isNewDefine(n, sc) {
break
}
if sym, level, ok := sc.lookup(n.ident); ok {
// Found symbol, populate node info
n.typ, n.findex, n.level = sym.typ, sym.index, level
if n.findex < 0 {
n.val = sym.node
} else {
n.sym = sym
switch {
case sym.kind == constSym && sym.rval.IsValid():
n.rval = sym.rval
n.kind = basicLit
case n.ident == "iota":
n.rval = reflect.ValueOf(sc.iota)
n.kind = basicLit
case n.ident == "nil":
n.kind = basicLit
case sym.kind == binSym:
n.typ = sym.typ
n.rval = sym.rval
case sym.kind == bltnSym:
if n.anc.kind != callExpr {
err = n.cfgErrorf("use of builtin %s not in function call", n.ident)
}
if n.anc.kind == funcDecl && n.anc.child[1] == n {
// Dont process a function name identExpr.
break
}
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)
break
}
}
// Found symbol, populate node info
n.typ, n.findex, n.level = sym.typ, sym.index, level
if n.findex < 0 {
n.val = sym.node
} else {
n.sym = sym
switch {
case sym.kind == constSym && sym.rval.IsValid():
n.rval = sym.rval
n.kind = basicLit
case n.ident == "iota":
n.rval = reflect.ValueOf(sc.iota)
n.kind = basicLit
n.typ.untyped = true
case n.ident == "nil":
n.kind = basicLit
case sym.kind == binSym:
n.typ = sym.typ
n.rval = sym.rval
case sym.kind == bltnSym:
if n.anc.kind != callExpr {
err = n.cfgErrorf("use of builtin %s not in function call", n.ident)
}
}
if n.sym != nil {
n.recv = n.sym.recv
}
} else {
err = n.cfgErrorf("undefined: %s", n.ident)
}
if n.sym != nil {
n.recv = n.sym.recv
}
case ifStmt0: // if cond {}
@@ -1213,12 +1328,13 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
wireChild(n)
c := n.lastChild()
n.findex = c.findex
n.level = c.level
n.typ = c.typ
n.rval = c.rval
case rangeStmt:
if sc.rangeChanType(n) != nil {
n.start = n.child[1] // Get chan
n.start = n.child[1].start // Get chan
n.child[1].tnext = n // then go to range function
n.tnext = n.child[2].start // then go to range body
n.child[2].tnext = n // then body go to range function (loop)
@@ -1230,7 +1346,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
} else {
k, o, body = n.child[0], n.child[1], n.child[2]
}
n.start = o // Get array or map object
n.start = o.start // Get array or map object
o.tnext = k.start // then go to iterator init
k.tnext = n // then go to range function
n.tnext = body.start // then go to range body
@@ -1615,7 +1731,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
}
if n.anc.action != aAssignX {
if n.child[0].typ.cat == valueT {
if n.child[0].typ.cat == valueT && !isStruct(n.child[1].typ) {
// Avoid special wrapping of interfaces and func types.
n.typ = &itype{cat: valueT, rtype: n.child[1].typ.TypeOf()}
} else {
@@ -1679,6 +1795,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
dest := n.anc.child[childPos(n)-n.anc.nright]
n.typ = dest.typ
n.findex = dest.findex
n.level = dest.level
case n.anc.kind == returnStmt:
pos := childPos(n)
n.typ = sc.def.typ.ret[pos]
@@ -1791,13 +1908,13 @@ func compDefineX(sc *scope, n *node) error {
}
// TODO used for allocation optimization, temporarily disabled
//func isAncBranch(n *node) bool {
// func isAncBranch(n *node) bool {
// switch n.anc.kind {
// case If0, If1, If2, If3:
// return true
// }
// return false
//}
// }
func childPos(n *node) int {
for i, c := range n.anc.child {
@@ -1837,6 +1954,90 @@ func genRun(nod *node) error {
return err
}
func genGlobalVars(roots []*node, sc *scope) (*node, error) {
var vars []*node
for _, n := range roots {
vars = append(vars, getVars(n)...)
}
if len(vars) == 0 {
return nil, nil
}
varNode, err := genGlobalVarDecl(vars, sc)
if err != nil {
return nil, err
}
setExec(varNode.start)
return varNode, nil
}
func getVars(n *node) (vars []*node) {
for _, child := range n.child {
if child.kind == varDecl {
vars = append(vars, child.child...)
}
}
return vars
}
func genGlobalVarDecl(nodes []*node, sc *scope) (*node, error) {
varNode := &node{kind: varDecl, action: aNop, gen: nop}
deps := map[*node][]*node{}
for _, n := range nodes {
deps[n] = getVarDependencies(n, sc)
}
inited := map[*node]bool{}
revisit := []*node{}
for {
for _, n := range nodes {
canInit := true
for _, d := range deps[n] {
if !inited[d] {
canInit = false
}
}
if !canInit {
revisit = append(revisit, n)
continue
}
varNode.child = append(varNode.child, n)
inited[n] = true
}
if len(revisit) == 0 || equalNodes(nodes, revisit) {
break
}
nodes = revisit
revisit = []*node{}
}
if len(revisit) > 0 {
return nil, revisit[0].cfgErrorf("variable definition loop")
}
wireChild(varNode)
return varNode, nil
}
func getVarDependencies(nod *node, sc *scope) (deps []*node) {
nod.Walk(func(n *node) bool {
if n.kind == identExpr {
if sym, _, ok := sc.lookup(n.ident); ok {
if sym.kind != varSym || !sym.global || sym.node == nod {
return false
}
deps = append(deps, sym.node)
}
}
return true
}, nil)
return deps
}
// setFnext sets the cond fnext field to next, propagates it for parenthesis blocks
// and sets the action to branch.
func setFNext(cond, next *node) {
@@ -1869,10 +2070,10 @@ func getDefault(n *node) int {
func isBinType(v reflect.Value) bool { return v.IsValid() && v.Kind() == reflect.Ptr && v.IsNil() }
// isType returns true if node refers to a type definition, false otherwise
// isType returns true if node refers to a type definition, false otherwise.
func (n *node) isType(sc *scope) bool {
switch n.kind {
case arrayType, chanType, funcType, interfaceType, mapType, structType:
case arrayType, chanType, chanTypeRecv, chanTypeSend, funcType, interfaceType, mapType, structType:
return true
case parenExpr, starExpr:
if len(n.child) == 1 {
@@ -1895,48 +2096,69 @@ func (n *node) isType(sc *scope) bool {
return false
}
// wireChild wires AST nodes for CFG in subtree
func wireChild(n *node) {
// wireChild wires AST nodes for CFG in subtree.
func wireChild(n *node, exclude ...nkind) {
child := excludeNodeKind(n.child, exclude)
// Set start node, in subtree (propagated to ancestors by post-order processing)
for _, child := range n.child {
switch child.kind {
case arrayType, chanType, funcDecl, importDecl, mapType, basicLit, identExpr, typeDecl:
for _, c := range child {
switch c.kind {
case arrayType, chanType, chanTypeRecv, chanTypeSend, funcDecl, importDecl, mapType, basicLit, identExpr, typeDecl:
continue
default:
n.start = child.start
n.start = c.start
}
break
}
// Chain sequential operations inside a block (next is right sibling)
for i := 1; i < len(n.child); i++ {
switch n.child[i].kind {
for i := 1; i < len(child); i++ {
switch child[i].kind {
case funcDecl:
n.child[i-1].tnext = n.child[i]
child[i-1].tnext = child[i]
default:
switch n.child[i-1].kind {
switch child[i-1].kind {
case breakStmt, continueStmt, gotoStmt, returnStmt:
// tnext is already computed, no change
default:
n.child[i-1].tnext = n.child[i].start
child[i-1].tnext = child[i].start
}
}
}
// Chain subtree next to self
for i := len(n.child) - 1; i >= 0; i-- {
switch n.child[i].kind {
case arrayType, chanType, importDecl, mapType, funcDecl, basicLit, identExpr, typeDecl:
for i := len(child) - 1; i >= 0; i-- {
switch child[i].kind {
case arrayType, chanType, chanTypeRecv, chanTypeSend, importDecl, mapType, funcDecl, basicLit, identExpr, typeDecl:
continue
case breakStmt, continueStmt, gotoStmt, returnStmt:
// tnext is already computed, no change
default:
n.child[i].tnext = n
child[i].tnext = n
}
break
}
}
func excludeNodeKind(child []*node, kinds []nkind) []*node {
if len(kinds) == 0 {
return child
}
var res []*node
for _, c := range child {
exclude := false
for _, k := range kinds {
if c.kind == k {
exclude = true
}
}
if !exclude {
res = append(res, c)
}
}
return res
}
func (n *node) name() (s string) {
switch {
case n.ident != "":
@@ -1947,7 +2169,7 @@ func (n *node) name() (s string) {
return s
}
// isInteger returns true if node type is integer, false otherwise
// isInteger returns true if node type is integer, false otherwise.
func (n *node) isInteger() bool {
if isInt(n.typ.TypeOf()) {
return true
@@ -1966,11 +2188,25 @@ func (n *node) isInteger() bool {
return true
}
}
if isConstantValue(t) {
c := n.rval.Interface().(constant.Value)
switch c.Kind() {
case constant.Int:
return true
case constant.Float:
f, _ := constant.Float64Val(c)
if f == math.Trunc(f) {
n.rval = reflect.ValueOf(constant.ToInt(c))
n.typ.rtype = n.rval.Type()
return true
}
}
}
}
return false
}
// isNatural returns true if node type is natural, false otherwise
// isNatural returns true if node type is natural, false otherwise.
func (n *node) isNatural() bool {
if isUint(n.typ.TypeOf()) {
return true
@@ -1993,14 +2229,31 @@ func (n *node) isNatural() bool {
return true
}
}
if isConstantValue(t) {
c := n.rval.Interface().(constant.Value)
switch c.Kind() {
case constant.Int:
i, _ := constant.Int64Val(c)
if i >= 0 {
return true
}
case constant.Float:
f, _ := constant.Float64Val(c)
if f == math.Trunc(f) {
n.rval = reflect.ValueOf(constant.ToInt(c))
n.typ.rtype = n.rval.Type()
return true
}
}
}
}
return false
}
// isNil returns true if node is a literal nil value, false otherwise
// isNil returns true if node is a literal nil value, false otherwise.
func (n *node) isNil() bool { return n.kind == basicLit && !n.rval.IsValid() }
// fieldType returns the nth parameter field node (type) of a fieldList node
// fieldType returns the nth parameter field node (type) of a fieldList node.
func (n *node) fieldType(m int) *node {
k := 0
l := len(n.child)
@@ -2023,7 +2276,7 @@ func (n *node) fieldType(m int) *node {
return nil
}
// lastChild returns the last child of a node
// lastChild returns the last child of a node.
func (n *node) lastChild() *node { return n.child[len(n.child)-1] }
func isKey(n *node) bool {
@@ -2039,10 +2292,20 @@ func isField(n *node) bool {
}
func isRecursiveField(n *node) bool {
return isField(n) && (n.typ.recursive || n.typ.cat == ptrT && n.typ.val.recursive)
if !isField(n) {
return false
}
t := n.typ
for t != nil {
if t.recursive {
return true
}
t = t.val
}
return false
}
// isNewDefine returns true if node refers to a new definition
// isNewDefine returns true if node refers to a new definition.
func isNewDefine(n *node, sc *scope) bool {
if n.ident == "_" {
return true
@@ -2234,12 +2497,15 @@ func arrayTypeLen(n *node) int {
return max + 1
}
// isValueUntyped returns true if value is untyped
// isValueUntyped returns true if value is untyped.
func isValueUntyped(v reflect.Value) bool {
// Consider only constant values.
if v.CanSet() {
return false
}
t := v.Type()
if t.Implements(constVal) {
return true
}
return t.String() == t.Kind().String()
}

View File

@@ -10,7 +10,7 @@ import (
"strings"
)
// astDot displays an AST in graphviz dot(1) format using dotty(1) co-process
// astDot displays an AST in graphviz dot(1) format using dotty(1) co-process.
func (n *node) astDot(out io.Writer, name string) {
fmt.Fprintf(out, "digraph ast {\n")
fmt.Fprintf(out, "labelloc=\"t\"\n")
@@ -36,7 +36,7 @@ func (n *node) astDot(out io.Writer, name string) {
fmt.Fprintf(out, "}\n")
}
// cfgDot displays a CFG in graphviz dot(1) format using dotty(1) co-process
// cfgDot displays a CFG in graphviz dot(1) format using dotty(1) co-process.
func (n *node) cfgDot(out io.Writer) {
fmt.Fprintf(out, "digraph cfg {\n")
n.Walk(nil, func(n *node) {
@@ -66,7 +66,7 @@ type nopCloser struct {
func (nopCloser) Close() error { return nil }
// dotWriter returns an output stream to a dot(1) co-process where to write data in .dot format
// dotWriter returns an output stream to a dot(1) co-process where to write data in .dot format.
func dotWriter(dotCmd string) io.WriteCloser {
if dotCmd == "" {
return nopCloser{ioutil.Discard}

View File

@@ -7,7 +7,7 @@ import (
"github.com/containous/yaegi/interp"
)
// Generic example
// Generic example.
func Example_eval() {
// Create a new interpreter context
i := interp.New(interp.Options{})

View File

@@ -1,6 +1,9 @@
package interp
import "reflect"
import (
"path/filepath"
"reflect"
)
// gta performs a global types analysis on the AST, registering types,
// variables and functions symbols at package level, prior to CFG.
@@ -11,6 +14,8 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
var err error
var revisit []*node
baseName := filepath.Base(interp.fset.Position(root.pos).Filename)
root.Walk(func(n *node) bool {
if err != nil {
return false
@@ -73,8 +78,8 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
if typ.isBinMethod {
typ = &itype{cat: valueT, rtype: typ.methodCallType(), isBinMethod: true, scope: sc}
}
if sc.sym[dest.ident] == nil {
sc.sym[dest.ident] = &symbol{kind: varSym, global: true, index: sc.add(typ), typ: typ, rval: val}
if sc.sym[dest.ident] == nil || sc.sym[dest.ident].typ.incomplete {
sc.sym[dest.ident] = &symbol{kind: varSym, global: true, index: sc.add(typ), typ: typ, rval: val, node: n}
}
if n.anc.kind == constDecl {
sc.sym[dest.ident].kind = constSym
@@ -103,17 +108,43 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
}
}
for _, c := range n.child[:l] {
sc.sym[c.ident] = &symbol{index: sc.add(n.typ), kind: varSym, global: true, typ: n.typ}
asImportName := filepath.Join(c.ident, baseName)
sym1, exists1 := sc.sym[asImportName]
sym2, exists2 := sc.sym[c.ident]
if !exists1 && !exists2 {
sc.sym[c.ident] = &symbol{index: sc.add(n.typ), kind: varSym, global: true, typ: n.typ, node: n}
continue
}
var sym *symbol
if exists1 {
// prev declaration is an import statement
sym = sym1
} else {
// prev declaration is whatever else (var, type, etc)
sym = sym2
}
// redeclaration error
if sym.typ.node != nil && sym.typ.node.anc != nil {
prevDecl := n.interp.fset.Position(sym.typ.node.anc.pos)
err = n.cfgErrorf("%s redeclared in this block\n\tprevious declaration at %v", c.ident, prevDecl)
return false
}
err = n.cfgErrorf("%s redeclared in this block", c.ident)
return false
}
case funcDecl:
if n.typ, err = nodeType(interp, sc, n.child[2]); err != nil {
return false
}
if isMethod(n) {
ident := n.child[1].ident
switch {
case isMethod(n):
// TODO(mpl): redeclaration detection
// Add a method symbol in the receiver type name space
var rcvrtype *itype
n.ident = n.child[1].ident
n.ident = ident
rcvr := n.child[0].child[0]
rtn := rcvr.lastChild()
typeName := rtn.ident
@@ -138,8 +169,32 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
}
rcvrtype.method = append(rcvrtype.method, n)
n.child[0].child[0].lastChild().typ = rcvrtype
} else {
// Add a function symbol in the package name space
case ident == "init":
// TODO(mpl): use constant instead of hardcoded string?
// init functions do not get declared as per the Go spec.
default:
asImportName := filepath.Join(ident, baseName)
if _, exists := sc.sym[asImportName]; exists {
// redeclaration error
// TODO(mpl): improve error with position of previous declaration.
err = n.cfgErrorf("%s redeclared in this block", ident)
return false
}
sym, exists := sc.sym[ident]
if exists {
// Make sure the symbol we found seems to be about another node, before calling
// it a redeclaration.
if sym.typ.isComplete() {
// TODO(mpl): this check might be too permissive?
if sym.kind != funcSym || sym.typ.cat != n.typ.cat || sym.node != n || sym.index != -1 {
// redeclaration error
// TODO(mpl): improve error with position of previous declaration.
err = n.cfgErrorf("%s redeclared in this block", ident)
return false
}
}
}
// Add a function symbol in the package name space except for init
sc.sym[n.child[1].ident] = &symbol{kind: funcSym, typ: n.typ, node: n, index: -1}
}
if !n.typ.isComplete() {
@@ -172,8 +227,20 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
if name == "" {
name = identifier.FindString(ipath)
}
// imports of a same package are all mapped in the same scope, so we cannot just
// map them by their names, otherwise we could have collisions from same-name
// imports in different source files of the same package. Therefore, we suffix
// the key with the basename of the source file.
name = filepath.Join(name, baseName)
if _, exists := sc.sym[name]; !exists {
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: binPkgT, path: ipath, scope: sc}}
break
}
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: binPkgT, path: ipath, scope: sc}}
// redeclaration error
// TODO(mpl): find position information about previous declaration
err = n.cfgErrorf("%s redeclared in this block", name)
return false
}
} else if pkgName, err = interp.importSrc(rpath, ipath); err == nil {
sc.types = interp.universe.types
@@ -191,6 +258,7 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
}
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT, path: ipath, scope: sc}}
// TODO(mpl): redecleration detection
}
} else {
err = n.cfgErrorf("import %q error: %v", ipath, err)
@@ -202,6 +270,7 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
if typ, err = nodeType(interp, sc, n.child[1]); err != nil {
return false
}
if n.child[1].kind == identExpr {
n.typ = &itype{cat: aliasT, val: typ, name: typeName, path: rpath, field: typ.field, incomplete: typ.incomplete, scope: sc, node: n.child[0]}
copy(n.typ.method, typ.method)
@@ -210,11 +279,26 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
n.typ.name = typeName
n.typ.path = rpath
}
// Type may be already declared for a receiver in a method function
if sc.sym[typeName] == nil {
asImportName := filepath.Join(typeName, baseName)
if _, exists := sc.sym[asImportName]; exists {
// redeclaration error
// TODO(mpl): improve error with position of previous declaration.
err = n.cfgErrorf("%s redeclared in this block", typeName)
return false
}
sym, exists := sc.sym[typeName]
if !exists {
sc.sym[typeName] = &symbol{kind: typeSym}
} else {
n.typ.method = append(n.typ.method, sc.sym[typeName].typ.method...)
if sym.typ != nil && (len(sym.typ.method) > 0) {
// Type has already been seen as a receiver in a method function
n.typ.method = append(n.typ.method, sym.typ.method...)
} else {
// TODO(mpl): figure out how to detect redeclarations without breaking type aliases.
// Allow redeclarations for now.
sc.sym[typeName] = &symbol{kind: typeSym}
}
}
sc.sym[typeName].typ = n.typ
if !n.typ.isComplete() {

30
interp/hooks.go Normal file
View File

@@ -0,0 +1,30 @@
package interp
import "reflect"
const hooksPath = "github.com/containous/yaegi"
// convertFn is the signature of a symbol converter.
type convertFn func(from, to reflect.Type) func(src, dest reflect.Value)
// hooks are external symbol bindings.
type hooks struct {
convert []convertFn
}
func (h *hooks) Parse(m map[string]reflect.Value) {
if con, ok := getConvertFn(m["convert"]); ok {
h.convert = append(h.convert, con)
}
}
func getConvertFn(v reflect.Value) (convertFn, bool) {
if !v.IsValid() {
return nil, false
}
fn, ok := v.Interface().(func(from, to reflect.Type) func(src, dest reflect.Value))
if !ok {
return nil, false
}
return fn, true
}

View File

@@ -18,7 +18,7 @@ import (
"sync/atomic"
)
// Interpreter node structure for AST and CFG
// Interpreter node structure for AST and CFG.
type node struct {
child []*node // child subtrees (AST)
anc *node // ancestor (AST)
@@ -46,14 +46,14 @@ type node struct {
ident string // set if node is a var or func
}
// receiver stores method receiver object access path
// receiver stores method receiver object access path.
type receiver struct {
node *node // receiver value for alias and struct types
val reflect.Value // receiver value for interface type and value type
index []int // path in receiver value for interface or value type
}
// frame contains values for the current execution level (a function context)
// frame contains values for the current execution level (a function context).
type frame struct {
// id is an atomic counter used for cancellation, only access
// via newFrame/runid/setrunid/clone.
@@ -96,13 +96,13 @@ func (f *frame) clone() *frame {
}
}
// Exports stores the map of binary packages per package path
// Exports stores the map of binary packages per package path.
type Exports map[string]map[string]reflect.Value
// imports stores the map of source packages per package path
// imports stores the map of source packages per package path.
type imports map[string]map[string]*symbol
// opt stores interpreter options
// opt stores interpreter options.
type opt struct {
astDot bool // display AST graph (debug)
cfgDot bool // display CFG graph (debug)
@@ -114,7 +114,7 @@ type opt struct {
context build.Context // build context: GOPATH, build constraints
}
// Interpreter contains global resources and state
// Interpreter contains global resources and state.
type Interpreter struct {
// id is an atomic counter counter used for run cancellation,
// only accessed via runid/stop
@@ -138,6 +138,8 @@ type Interpreter struct {
srcPkg imports // source packages used in interpreter, indexed by path
pkgNames map[string]string // package names, indexed by path
done chan struct{} // for cancellation of channel operations
hooks *hooks // symbol hooks
}
const (
@@ -145,7 +147,7 @@ const (
selfPath = "github.com/containous/yaegi/interp"
)
// Symbols exposes interpreter values
// Symbols exposes interpreter values.
var Symbols = Exports{
selfPath: map[string]reflect.Value{
"New": reflect.ValueOf(New),
@@ -157,7 +159,7 @@ var Symbols = Exports{
func init() { Symbols[selfPath]["Symbols"] = reflect.ValueOf(Symbols) }
// _error is a wrapper of error interface type
// _error is a wrapper of error interface type.
type _error struct {
WError func() string
}
@@ -204,7 +206,7 @@ type Options struct {
BuildTags []string
}
// New returns a new interpreter
// New returns a new interpreter.
func New(options Options) *Interpreter {
i := Interpreter{
opt: opt{context: build.Default},
@@ -216,6 +218,7 @@ func New(options Options) *Interpreter {
srcPkg: imports{},
pkgNames: map[string]string{},
rdir: map[string]bool{},
hooks: &hooks{},
}
i.opt.context.GOPATH = options.GoPath
@@ -295,7 +298,7 @@ func initUniverse() *scope {
return sc
}
// resizeFrame resizes the global frame of interpreter
// resizeFrame resizes the global frame of interpreter.
func (interp *Interpreter) resizeFrame() {
l := len(interp.universe.types)
b := len(interp.frame.data)
@@ -320,7 +323,7 @@ func (interp *Interpreter) main() *node {
}
// Eval evaluates Go code represented as a string. It returns a map on
// current interpreted package exported symbols
// current interpreted package exported symbols.
func (interp *Interpreter) Eval(src string) (res reflect.Value, err error) {
defer func() {
r := recover()
@@ -402,6 +405,13 @@ func (interp *Interpreter) Eval(src string) (res reflect.Value, err error) {
// Execute node closures
interp.run(root, nil)
// Wire and execute global vars
n, err := genGlobalVars([]*node{root}, interp.scopes[interp.Name])
if err != nil {
return res, err
}
interp.run(n, nil)
for _, n := range initNodes {
interp.run(n, interp.frame)
}
@@ -454,7 +464,7 @@ func (interp *Interpreter) stop() {
func (interp *Interpreter) runid() uint64 { return atomic.LoadUint64(&interp.id) }
// getWrapper returns the wrapper type of the corresponding interface, or nil if not found
// getWrapper returns the wrapper type of the corresponding interface, or nil if not found.
func (interp *Interpreter) getWrapper(t reflect.Type) reflect.Type {
if p, ok := interp.binPkg[t.PkgPath()]; ok {
return p["_"+t.Name()].Type().Elem()
@@ -463,10 +473,22 @@ func (interp *Interpreter) getWrapper(t reflect.Type) reflect.Type {
}
// Use loads binary runtime symbols in the interpreter context so
// they can be used in interpreted code
// they can be used in interpreted code.
func (interp *Interpreter) Use(values Exports) {
for k, v := range values {
interp.binPkg[k] = v
if k == hooksPath {
interp.hooks.Parse(v)
continue
}
if interp.binPkg[k] == nil {
interp.binPkg[k] = v
continue
}
for s, sym := range v {
interp.binPkg[k][s] = sym
}
}
}
@@ -523,7 +545,7 @@ func (interp *Interpreter) REPL(in io.Reader, out io.Writer) {
// Repl performs a Read-Eval-Print-Loop on input file descriptor.
// Results are printed on output.
// Deprecated: use REPL instead
// Deprecated: use REPL instead.
func (interp *Interpreter) Repl(in, out *os.File) {
interp.REPL(in, out)
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/containous/yaegi/interp"
"github.com/containous/yaegi/stdlib"
"github.com/containous/yaegi/stdlib/unsafe"
)
func TestInterpConsistencyBuild(t *testing.T) {
@@ -34,6 +35,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
if filepath.Ext(file.Name()) != ".go" ||
file.Name() == "assign11.go" || // expect error
file.Name() == "assign12.go" || // expect error
file.Name() == "assign15.go" || // expect error
file.Name() == "bad0.go" || // expect error
file.Name() == "const9.go" || // expect error
file.Name() == "export1.go" || // non-main package
@@ -43,8 +45,11 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "fun22.go" || // expect error
file.Name() == "if2.go" || // expect error
file.Name() == "import6.go" || // expect error
file.Name() == "init1.go" || // expect error
file.Name() == "io0.go" || // use random number
file.Name() == "op1.go" || // expect error
file.Name() == "op7.go" || // expect error
file.Name() == "op9.go" || // expect error
file.Name() == "bltn0.go" || // expect error
file.Name() == "method16.go" || // private struct field
file.Name() == "switch8.go" || // expect error
@@ -64,6 +69,17 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "redeclaration3.go" || // expect error
file.Name() == "redeclaration4.go" || // expect error
file.Name() == "redeclaration5.go" || // expect error
file.Name() == "redeclaration-global0.go" || // expect error
file.Name() == "redeclaration-global1.go" || // expect error
file.Name() == "redeclaration-global2.go" || // expect error
file.Name() == "redeclaration-global3.go" || // expect error
file.Name() == "redeclaration-global4.go" || // expect error
file.Name() == "redeclaration-global5.go" || // expect error
file.Name() == "redeclaration-global6.go" || // expect error
file.Name() == "restricted0.go" || // expect error
file.Name() == "restricted1.go" || // expect error
file.Name() == "restricted2.go" || // expect error
file.Name() == "restricted3.go" || // expect error
file.Name() == "server6.go" || // syntax parsing
file.Name() == "server5.go" || // syntax parsing
file.Name() == "server4.go" || // syntax parsing
@@ -72,7 +88,8 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "server1a.go" || // syntax parsing
file.Name() == "server1.go" || // syntax parsing
file.Name() == "server0.go" || // syntax parsing
file.Name() == "server.go" { // syntax parsing
file.Name() == "server.go" || // syntax parsing
file.Name() == "range9.go" { // expect error
continue
}
@@ -97,6 +114,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
i.Name = filePath
i.Use(stdlib.Symbols)
i.Use(interp.Symbols)
i.Use(unsafe.Symbols)
_, err = i.Eval(string(src))
if err != nil {

View File

@@ -34,24 +34,24 @@ func TestEvalArithmetic(t *testing.T) {
{desc: "add_FI", src: "2.3 + 3", res: "5.3"},
{desc: "add_IF", src: "2 + 3.3", res: "5.3"},
{desc: "add_SS", src: `"foo" + "bar"`, res: "foobar"},
{desc: "add_SI", src: `"foo" + 1`, err: "1:28: illegal operand types for '+' operator"},
{desc: "sub_SS", src: `"foo" - "bar"`, err: "1:28: illegal operand types for '-' operator"},
{desc: "add_SI", src: `"foo" + 1`, err: "1:28: invalid operation: mismatched types string and int"},
{desc: "sub_SS", src: `"foo" - "bar"`, err: "1:28: invalid operation: operator - not defined on string"},
{desc: "sub_II", src: "7 - 3", res: "4"},
{desc: "sub_FI", src: "7.2 - 3", res: "4.2"},
{desc: "sub_IF", src: "7 - 3.2", res: "3.8"},
{desc: "mul_II", src: "2 * 3", res: "6"},
{desc: "mul_FI", src: "2.2 * 3", res: "6.6000000000000005"},
{desc: "mul_IF", src: "3 * 2.2", res: "6.6000000000000005"},
{desc: "rem_FI", src: "8.2 % 4", err: "1:28: illegal operand types for '%' operator"},
{desc: "mul_FI", src: "2.2 * 3", res: "6.6"},
{desc: "mul_IF", src: "3 * 2.2", res: "6.6"},
{desc: "rem_FI", src: "8.2 % 4", err: "1:28: invalid operation: operator % not defined on float64"},
{desc: "shl_II", src: "1 << 8", res: "256"},
{desc: "shl_IN", src: "1 << -1", err: "1:28: illegal operand types for '<<' operator"},
{desc: "shl_IN", src: "1 << -1", err: "1:28: invalid operation: shift count type int, must be integer"},
{desc: "shl_IF", src: "1 << 1.0", res: "2"},
{desc: "shl_IF1", src: "1 << 1.1", err: "1:28: illegal operand types for '<<' operator"},
{desc: "shl_IF1", src: "1 << 1.1", err: "1:28: invalid operation: shift count type float64, must be integer"},
{desc: "shl_IF2", src: "1.0 << 1", res: "2"},
{desc: "shr_II", src: "1 >> 8", res: "0"},
{desc: "shr_IN", src: "1 >> -1", err: "1:28: illegal operand types for '>>' operator"},
{desc: "shr_IN", src: "1 >> -1", err: "1:28: invalid operation: shift count type int, must be integer"},
{desc: "shr_IF", src: "1 >> 1.0", res: "0"},
{desc: "shr_IF1", src: "1 >> 1.1", err: "1:28: illegal operand types for '>>' operator"},
{desc: "shr_IF1", src: "1 >> 1.1", err: "1:28: invalid operation: shift count type float64, must be integer"},
{desc: "neg_I", src: "-2", res: "-2"},
{desc: "pos_I", src: "+2", res: "2"},
{desc: "bitnot_I", src: "^2", res: "-3"},
@@ -300,7 +300,7 @@ func TestEvalComparison(t *testing.T) {
var b = Bar("test")
var c = a == b
`,
err: "7:13: mismatched types main.Foo and main.Bar",
err: "7:13: invalid operation: mismatched types main.Foo and main.Bar",
},
})
}
@@ -385,6 +385,25 @@ func TestEvalChan(t *testing.T) {
})
}
func TestEvalFunctionCallWithFunctionParam(t *testing.T) {
i := interp.New(interp.Options{})
eval(t, i, `
func Bar(s string, fn func(string)string) string { return fn(s) }
`)
v := eval(t, i, "Bar")
bar := v.Interface().(func(string, func(string) string) string)
got := bar("hello ", func(s string) string {
return s + "world!"
})
want := "hello world!"
if got != want {
t.Errorf("unexpected result of function eval: got %q, want %q", got, want)
}
}
func TestEvalMissingSymbol(t *testing.T) {
defer func() {
r := recover()
@@ -483,7 +502,7 @@ func TestEvalWithContext(t *testing.T) {
// Successful cancellation.
// Check we can still execute an expression.
v, err := i.EvalWithContext(context.Background(), "1+1\n") //nolint:govet
v, err := i.EvalWithContext(context.Background(), "1+1\n")
if err != nil {
t.Errorf("failed to evaluate expression after cancellation: %v", err)
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/containous/yaegi/interp"
"github.com/containous/yaegi/stdlib"
"github.com/containous/yaegi/stdlib/unsafe"
)
func TestFile(t *testing.T) {
@@ -56,6 +57,7 @@ func runCheck(t *testing.T, p string) {
i.Name = p
i.Use(interp.Symbols)
i.Use(stdlib.Symbols)
i.Use(unsafe.Symbols)
_, err = i.Eval(string(src))
if errWanted {

View File

@@ -2,7 +2,11 @@ package interp
// Code generated by 'go run ../internal/genop/genop.go'. DO NOT EDIT.
import "reflect"
import (
"go/constant"
"go/token"
"reflect"
)
// Arithmetic operators
@@ -150,9 +154,16 @@ func add(n *node) {
func addConst(n *node) {
v0, v1 := n.child[0].rval, n.child[1].rval
isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type()))
t := n.typ.rtype
if isConst {
t = constVal
}
n.rval = reflect.New(t).Elem()
switch {
case isConst:
v := constant.BinaryOp(vConstantValue(v0), token.ADD, vConstantValue(v1))
n.rval.Set(reflect.ValueOf(v))
case isString(t):
n.rval.SetString(v0.String() + v1.String())
case isComplex(t):
@@ -234,9 +245,16 @@ func and(n *node) {
func andConst(n *node) {
v0, v1 := n.child[0].rval, n.child[1].rval
isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type()))
t := n.typ.rtype
if isConst {
t = constVal
}
n.rval = reflect.New(t).Elem()
switch {
case isConst:
v := constant.BinaryOp(vConstantValue(v0), token.AND, vConstantValue(v1))
n.rval.Set(reflect.ValueOf(v))
case isUint(t):
n.rval.SetUint(vUint(v0) & vUint(v1))
case isInt(t):
@@ -312,9 +330,16 @@ func andNot(n *node) {
func andNotConst(n *node) {
v0, v1 := n.child[0].rval, n.child[1].rval
isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type()))
t := n.typ.rtype
if isConst {
t = constVal
}
n.rval = reflect.New(t).Elem()
switch {
case isConst:
v := constant.BinaryOp(vConstantValue(v0), token.AND_NOT, vConstantValue(v1))
n.rval.Set(reflect.ValueOf(v))
case isUint(t):
n.rval.SetUint(vUint(v0) &^ vUint(v1))
case isInt(t):
@@ -442,9 +467,16 @@ func mul(n *node) {
func mulConst(n *node) {
v0, v1 := n.child[0].rval, n.child[1].rval
isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type()))
t := n.typ.rtype
if isConst {
t = constVal
}
n.rval = reflect.New(t).Elem()
switch {
case isConst:
v := constant.BinaryOp(vConstantValue(v0), token.MUL, vConstantValue(v1))
n.rval.Set(reflect.ValueOf(v))
case isComplex(t):
n.rval.SetComplex(vComplex(v0) * vComplex(v1))
case isFloat(t):
@@ -524,9 +556,16 @@ func or(n *node) {
func orConst(n *node) {
v0, v1 := n.child[0].rval, n.child[1].rval
isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type()))
t := n.typ.rtype
if isConst {
t = constVal
}
n.rval = reflect.New(t).Elem()
switch {
case isConst:
v := constant.BinaryOp(vConstantValue(v0), token.OR, vConstantValue(v1))
n.rval.Set(reflect.ValueOf(v))
case isUint(t):
n.rval.SetUint(vUint(v0) | vUint(v1))
case isInt(t):
@@ -654,9 +693,16 @@ func quo(n *node) {
func quoConst(n *node) {
v0, v1 := n.child[0].rval, n.child[1].rval
isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type()))
t := n.typ.rtype
if isConst {
t = constVal
}
n.rval = reflect.New(t).Elem()
switch {
case isConst:
v := constant.BinaryOp(vConstantValue(v0), token.QUO, vConstantValue(v1))
n.rval.Set(reflect.ValueOf(v))
case isComplex(t):
n.rval.SetComplex(vComplex(v0) / vComplex(v1))
case isFloat(t):
@@ -736,9 +782,16 @@ func rem(n *node) {
func remConst(n *node) {
v0, v1 := n.child[0].rval, n.child[1].rval
isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type()))
t := n.typ.rtype
if isConst {
t = constVal
}
n.rval = reflect.New(t).Elem()
switch {
case isConst:
v := constant.BinaryOp(vConstantValue(v0), token.REM, vConstantValue(v1))
n.rval.Set(reflect.ValueOf(v))
case isUint(t):
n.rval.SetUint(vUint(v0) % vUint(v1))
case isInt(t):
@@ -814,9 +867,17 @@ func shl(n *node) {
func shlConst(n *node) {
v0, v1 := n.child[0].rval, n.child[1].rval
isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type()))
t := n.typ.rtype
if isConst {
t = constVal
}
n.rval = reflect.New(t).Elem()
switch {
case isConst:
s, _ := constant.Uint64Val(vConstantValue(v1))
v := constant.Shift(vConstantValue(v0), token.SHL, uint(s))
n.rval.Set(reflect.ValueOf(v))
case isUint(t):
n.rval.SetUint(vUint(v0) << vUint(v1))
case isInt(t):
@@ -892,9 +953,17 @@ func shr(n *node) {
func shrConst(n *node) {
v0, v1 := n.child[0].rval, n.child[1].rval
isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type()))
t := n.typ.rtype
if isConst {
t = constVal
}
n.rval = reflect.New(t).Elem()
switch {
case isConst:
s, _ := constant.Uint64Val(vConstantValue(v1))
v := constant.Shift(vConstantValue(v0), token.SHR, uint(s))
n.rval.Set(reflect.ValueOf(v))
case isUint(t):
n.rval.SetUint(vUint(v0) >> vUint(v1))
case isInt(t):
@@ -1022,9 +1091,16 @@ func sub(n *node) {
func subConst(n *node) {
v0, v1 := n.child[0].rval, n.child[1].rval
isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type()))
t := n.typ.rtype
if isConst {
t = constVal
}
n.rval = reflect.New(t).Elem()
switch {
case isConst:
v := constant.BinaryOp(vConstantValue(v0), token.SUB, vConstantValue(v1))
n.rval.Set(reflect.ValueOf(v))
case isComplex(t):
n.rval.SetComplex(vComplex(v0) - vComplex(v1))
case isFloat(t):
@@ -1104,9 +1180,16 @@ func xor(n *node) {
func xorConst(n *node) {
v0, v1 := n.child[0].rval, n.child[1].rval
isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type()))
t := n.typ.rtype
if isConst {
t = constVal
}
n.rval = reflect.New(t).Elem()
switch {
case isConst:
v := constant.BinaryOp(vConstantValue(v0), token.XOR, vConstantValue(v1))
n.rval.Set(reflect.ValueOf(v))
case isUint(t):
n.rval.SetUint(vUint(v0) ^ vUint(v1))
case isInt(t):
@@ -1865,53 +1948,83 @@ func inc(n *node) {
}
func bitNotConst(n *node) {
v0 := n.child[0].rval
isConst := v0.IsValid() && isConstantValue(v0.Type())
t := n.typ.rtype
v := n.child[0].rval
if isConst {
t = constVal
}
n.rval = reflect.New(t).Elem()
switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
n.rval.SetInt(^v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
n.rval.SetUint(^v.Uint())
switch {
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())
}
}
func negConst(n *node) {
v0 := n.child[0].rval
isConst := v0.IsValid() && isConstantValue(v0.Type())
t := n.typ.rtype
v := n.child[0].rval
if isConst {
t = constVal
}
n.rval = reflect.New(t).Elem()
switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
n.rval.SetInt(-v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
n.rval.SetUint(-v.Uint())
case reflect.Float32, reflect.Float64:
n.rval.SetFloat(-v.Float())
case reflect.Complex64, reflect.Complex128:
n.rval.SetComplex(-v.Complex())
switch {
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 isFloat(t):
n.rval.SetFloat(-v0.Float())
case isComplex(t):
n.rval.SetComplex(-v0.Complex())
}
}
func notConst(n *node) {
v0 := n.child[0].rval
isConst := v0.IsValid() && isConstantValue(v0.Type())
t := n.typ.rtype
v := n.child[0].rval
if isConst {
t = constVal
}
n.rval = reflect.New(t).Elem()
n.rval.SetBool(!v.Bool())
if isConst {
v := constant.UnaryOp(token.NOT, vConstantValue(v0), 0)
n.rval.Set(reflect.ValueOf(v))
} else {
n.rval.SetBool(!v0.Bool())
}
}
func posConst(n *node) {
v0 := n.child[0].rval
isConst := v0.IsValid() && isConstantValue(v0.Type())
t := n.typ.rtype
v := n.child[0].rval
if isConst {
t = constVal
}
n.rval = reflect.New(t).Elem()
switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
n.rval.SetInt(+v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
n.rval.SetUint(+v.Uint())
case reflect.Float32, reflect.Float64:
n.rval.SetFloat(+v.Float())
case reflect.Complex64, reflect.Complex128:
n.rval.SetComplex(+v.Complex())
switch {
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 isFloat(t):
n.rval.SetFloat(+v0.Float())
case isComplex(t):
n.rval.SetComplex(+v0.Complex())
}
}
@@ -1920,6 +2033,80 @@ func equal(n *node) {
dest := genValueOutput(n, reflect.TypeOf(true))
c0, c1 := n.child[0], n.child[1]
if c0.typ.cat == aliasT || c1.typ.cat == aliasT {
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 != 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 == 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 != 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 == 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 != 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 == i1)
return tnext
}
}
}
return
}
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
case isString(t0) || isString(t1):
switch {
@@ -3505,6 +3692,80 @@ func notEqual(n *node) {
dest := genValueOutput(n, reflect.TypeOf(true))
c0, c1 := n.child[0], n.child[1]
if c0.typ.cat == aliasT || c1.typ.cat == aliasT {
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 != 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 != 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 != 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 != 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 != 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 != i1)
return tnext
}
}
}
return
}
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
case isString(t0) || isString(t1):
switch {

File diff suppressed because it is too large Load Diff

View File

@@ -6,10 +6,10 @@ import (
"strconv"
)
// A sKind represents the kind of symbol
// A sKind represents the kind of symbol.
type sKind uint
// Symbol kinds for the Go interpreter
// Symbol kinds for the Go interpreter.
const (
undefSym sKind = iota
binSym // Binary from runtime
@@ -86,7 +86,7 @@ type scope struct {
iota int // iota value in this scope
}
// push creates a new scope and chain it to the current one
// push creates a new scope and chain it to the current one.
func (s *scope) push(indirect bool) *scope {
sc := scope{anc: s, level: s.level, sym: map[string]*symbol{}}
if indirect {
@@ -117,13 +117,16 @@ func (s *scope) pop() *scope {
// lookup searches for a symbol in the current scope, and upper ones if not found
// it returns the symbol, the number of indirections level from the current scope
// and status (false if no result)
// and status (false if no result).
func (s *scope) lookup(ident string) (*symbol, int, bool) {
level := s.level
for s != nil {
for {
if sym, ok := s.sym[ident]; ok {
return sym, level - s.level, true
}
if s.anc == nil {
break
}
s = s.anc
}
return nil, 0, false
@@ -131,16 +134,43 @@ func (s *scope) lookup(ident string) (*symbol, int, bool) {
func (s *scope) rangeChanType(n *node) *itype {
if sym, _, found := s.lookup(n.child[1].ident); found {
if t := sym.typ; len(n.child) == 3 && t != nil && t.cat == chanT {
if t := sym.typ; len(n.child) == 3 && t != nil && (t.cat == chanT || t.cat == chanRecvT) {
return t
}
}
if c := n.child[1]; c.typ != nil && c.typ.cat == chanT {
return c.typ
c := n.child[1]
if c.typ == nil {
return nil
}
switch {
case c.typ.cat == chanT, c.typ.cat == chanRecvT:
return c.typ
case c.typ.cat == valueT && c.typ.rtype.Kind() == reflect.Chan:
return &itype{cat: chanT, val: &itype{cat: valueT, rtype: c.typ.rtype.Elem()}}
}
return nil
}
// fixType returns the input type, or a valid default type for untyped constant.
func (s *scope) fixType(t *itype) *itype {
if !t.untyped || t.cat != valueT {
return t
}
switch typ := t.TypeOf(); typ.Kind() {
case reflect.Int64:
return s.getType("int")
case reflect.Uint64:
return s.getType("uint")
case reflect.Float64:
return s.getType("float64")
case reflect.Complex128:
return s.getType("complex128")
}
return t
}
func (s *scope) getType(ident string) *itype {
var t *itype
if sym, _, found := s.lookup(ident); found {
@@ -151,7 +181,7 @@ func (s *scope) getType(ident string) *itype {
return t
}
// add adds a type to the scope types array, and returns its index
// add adds a type to the scope types array, and returns its index.
func (s *scope) add(typ *itype) (index int) {
if typ == nil {
log.Panic("nil type")

View File

@@ -22,12 +22,18 @@ func (interp *Interpreter) importSrc(rPath, path string) (string, error) {
// In all other cases, absolute import paths are resolved from the GOPATH
// and the nested "vendor" directories.
if isPathRelative(path) {
if rPath == "main" {
if rPath == mainID {
rPath = "."
}
dir = filepath.Join(filepath.Dir(interp.Name), rPath, path)
} else if dir, rPath, err = pkgDir(interp.context.GOPATH, rPath, path); err != nil {
return "", err
} else {
root, err := interp.rootFromSourceLocation(rPath)
if err != nil {
return "", err
}
if dir, rPath, err = pkgDir(interp.context.GOPATH, root, path); err != nil {
return "", err
}
}
if interp.rdir[path] {
@@ -126,6 +132,13 @@ func (interp *Interpreter) importSrc(rPath, path string) (string, error) {
interp.run(n, nil)
}
// Wire and execute global vars
n, err := genGlobalVars(rootNodes, interp.scopes[path])
if err != nil {
return "", err
}
interp.run(n, nil)
// Add main to list of functions to run, after all inits
if m := interp.main(); m != nil {
initNodes = append(initNodes, m)
@@ -138,6 +151,23 @@ func (interp *Interpreter) importSrc(rPath, path string) (string, error) {
return pkgName, nil
}
func (interp *Interpreter) rootFromSourceLocation(rPath string) (string, error) {
sourceFile := interp.Name
if rPath != mainID || !strings.HasSuffix(sourceFile, ".go") {
return rPath, nil
}
wd, err := os.Getwd()
if err != nil {
return "", err
}
pkgDir := filepath.Join(wd, filepath.Dir(sourceFile))
root := strings.TrimPrefix(pkgDir, filepath.Join(interp.context.GOPATH, "src")+"/")
if root == wd {
return "", fmt.Errorf("package location %s not in GOPATH", pkgDir)
}
return root, nil
}
// pkgDir returns the absolute path in filesystem for a package given its name and
// the root of the subtree dependencies.
func pkgDir(goPath string, root, path string) (string, string, error) {
@@ -158,13 +188,62 @@ func pkgDir(goPath string, root, path string) (string, string, error) {
return "", "", fmt.Errorf("unable to find source related to: %q", path)
}
return pkgDir(goPath, previousRoot(root), path)
rootPath := filepath.Join(goPath, "src", root)
prevRoot, err := previousRoot(rootPath, root)
if err != nil {
return "", "", err
}
return pkgDir(goPath, prevRoot, path)
}
// Find the previous source root. (vendor > vendor > ... > GOPATH)
func previousRoot(root string) string {
splitRoot := strings.Split(root, string(filepath.Separator))
const vendor = "vendor"
// Find the previous source root (vendor > vendor > ... > GOPATH).
func previousRoot(rootPath, root string) (string, error) {
rootPath = filepath.Clean(rootPath)
parent, final := filepath.Split(rootPath)
parent = filepath.Clean(parent)
// TODO(mpl): maybe it works for the special case main, but can't be bothered for now.
if root != mainID && final != vendor {
root = strings.TrimSuffix(root, string(filepath.Separator))
prefix := strings.TrimSuffix(rootPath, root)
// look for the closest vendor in one of our direct ancestors, as it takes priority.
var vendored string
for {
fi, err := os.Lstat(filepath.Join(parent, vendor))
if err == nil && fi.IsDir() {
vendored = strings.TrimPrefix(strings.TrimPrefix(parent, prefix), string(filepath.Separator))
break
}
if !os.IsNotExist(err) {
return "", err
}
// stop when we reach GOPATH/src/blah
parent = filepath.Dir(parent)
if parent == prefix {
break
}
// just an additional failsafe, stop if we reach the filesystem root.
// TODO(mpl): It should probably be a critical error actually,
// as we shouldn't have gone that high up in the tree.
if parent == string(filepath.Separator) {
break
}
}
if vendored != "" {
return vendored, nil
}
}
// TODO(mpl): the algorithm below might be redundant with the one above,
// but keeping it for now. Investigate/simplify/remove later.
splitRoot := strings.Split(root, string(filepath.Separator))
var index int
for i := len(splitRoot) - 1; i >= 0; i-- {
if splitRoot[i] == "vendor" {
@@ -174,10 +253,10 @@ func previousRoot(root string) string {
}
if index == 0 {
return ""
return "", nil
}
return filepath.Join(splitRoot[:index]...)
return filepath.Join(splitRoot[:index]...), nil
}
func effectivePkg(root, path string) string {
@@ -191,7 +270,8 @@ func effectivePkg(root, path string) string {
for i := 0; i < len(splitPath); i++ {
part := splitPath[len(splitPath)-1-i]
if part == splitRoot[len(splitRoot)-1-rootIndex] && i != 0 {
index := len(splitRoot) - 1 - rootIndex
if index > 0 && part == splitRoot[index] && i != 0 {
prevRootIndex = rootIndex
rootIndex++
} else if prevRootIndex == rootIndex {
@@ -207,7 +287,7 @@ func effectivePkg(root, path string) string {
return filepath.Join(root, frag)
}
// isPathRelative returns true if path starts with "./" or "../"
// isPathRelative returns true if path starts with "./" or "../".
func isPathRelative(s string) bool {
p := "." + string(filepath.Separator)
return strings.HasPrefix(s, p) || strings.HasPrefix(s, "."+p)

View File

@@ -26,6 +26,12 @@ func Test_effectivePkg(t *testing.T) {
path: "vendor/guthib.com/containous/vin",
expected: "github.com/foo/plugin/vendor/guthib.com/containous/fromage/vendor/guthib.com/containous/vin",
},
{
desc: "path is non-existent",
root: "foo",
path: "githib.com/foo/app",
expected: "foo/githib.com/foo/app",
},
}
for _, test := range testCases {
@@ -190,9 +196,10 @@ func Test_pkgDir(t *testing.T) {
func Test_previousRoot(t *testing.T) {
testCases := []struct {
desc string
root string
expected string
desc string
root string
rootPathSuffix string
expected string
}{
{
desc: "GOPATH",
@@ -209,6 +216,18 @@ func Test_previousRoot(t *testing.T) {
root: "github.com/foo/pkg/vendor/guthib.com/containous/fromage/vendor/guthib.com/containous/fuu",
expected: "github.com/foo/pkg/vendor/guthib.com/containous/fromage",
},
{
desc: "vendor is sibling",
root: "github.com/foo/bar",
rootPathSuffix: "testdata/src/github.com/foo/bar",
expected: "github.com/foo",
},
{
desc: "vendor is uncle",
root: "github.com/foo/bar/baz",
rootPathSuffix: "testdata/src/github.com/foo/bar/baz",
expected: "github.com/foo",
},
}
for _, test := range testCases {
@@ -216,7 +235,20 @@ func Test_previousRoot(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
p := previousRoot(test.root)
var rootPath string
if test.rootPathSuffix != "" {
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
rootPath = filepath.Join(wd, test.rootPathSuffix)
} else {
rootPath = vendor
}
p, err := previousRoot(rootPath, test.root)
if err != nil {
t.Error(err)
}
if p != test.expected {
t.Errorf("got: %s, want: %s", p, test.expected)

View File

@@ -0,0 +1 @@
package baz

View File

@@ -0,0 +1 @@
package whatever

View File

@@ -1,14 +1,17 @@
package interp
import (
"fmt"
"go/constant"
"path/filepath"
"reflect"
"strconv"
)
// tcat defines interpreter type categories
// tcat defines interpreter type categories.
type tcat uint
// Types for go language
// Types for go language.
const (
nilT tcat = iota
aliasT
@@ -18,6 +21,8 @@ const (
boolT
builtinT
chanT
chanSendT
chanRecvT
complex64T
complex128T
errorT
@@ -89,7 +94,7 @@ func (c tcat) String() string {
return "Cat(" + strconv.Itoa(int(c)) + ")"
}
// structField type defines a field in a struct
// structField type defines a field in a struct.
type structField struct {
name string
tag string
@@ -97,12 +102,12 @@ type structField struct {
typ *itype
}
// itype defines the internal representation of types in the interpreter
// itype defines the internal representation of types in the interpreter.
type itype struct {
cat tcat // Type category
field []structField // Array of struct fields if structT or interfaceT
key *itype // Type of key element if MapT or nil
val *itype // Type of value element if chanT, mapT, ptrT, aliasT, arrayT or variadicT
val *itype // Type of value element if chanT,chanSendT, chanRecvT, mapT, ptrT, aliasT, arrayT or variadicT
arg []*itype // Argument types if funcT or nil
ret []*itype // Return types if funcT or nil
method []*node // Associated methods or nil
@@ -119,7 +124,7 @@ type itype struct {
scope *scope // type declaration scope (in case of re-parse incomplete type)
}
// nodeType returns a type definition for the corresponding AST subtree
// nodeType returns a type definition for the corresponding AST subtree.
func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
if n.typ != nil && !n.typ.incomplete {
if n.kind == sliceExpr {
@@ -152,10 +157,16 @@ 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 n.child[0].rval.IsValid():
case v.IsValid():
// constant size
t.size = int(n.child[0].rval.Int())
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)
@@ -165,6 +176,8 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
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
}
@@ -231,6 +244,23 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
t.cat = stringT
t.name = "string"
t.untyped = true
case constant.Value:
switch v.Kind() {
case constant.Int:
t.cat = intT
t.name = "int"
t.untyped = true
case constant.Float:
t.cat = float64T
t.name = "float64"
t.untyped = true
case constant.Complex:
t.cat = complex128T
t.name = "complex128"
t.untyped = true
default:
err = n.cfgErrorf("missing support for type %v", n.rval)
}
default:
err = n.cfgErrorf("missing support for type %T: %v", v, n.rval)
}
@@ -355,6 +385,20 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
}
t.incomplete = t.val.incomplete
case chanTypeRecv:
t.cat = chanRecvT
if t.val, err = nodeType(interp, sc, n.child[0]); err != nil {
return nil, err
}
t.incomplete = t.val.incomplete
case chanTypeSend:
t.cat = chanSendT
if t.val, err = nodeType(interp, sc, n.child[0]); err != nil {
return nil, err
}
t.incomplete = t.val.incomplete
case ellipsisExpr:
t.cat = variadicT
if t.val, err = nodeType(interp, sc, n.child[0]); err != nil {
@@ -399,22 +443,30 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
}
case identExpr:
if sym, _, found := sc.lookup(n.ident); found {
t = sym.typ
if t.incomplete && t.node != n {
m := t.method
if t, err = nodeType(interp, sc, t.node); err != nil {
return nil, err
}
t.method = m
sym.typ = t
sym, _, found := sc.lookup(n.ident)
if !found {
// retry with the filename, in case ident is a package name.
// TODO(mpl): try to move that into lookup instead?
baseName := filepath.Base(interp.fset.Position(n.pos).Filename)
ident := filepath.Join(n.ident, baseName)
sym, _, found = sc.lookup(ident)
if !found {
t.incomplete = true
sc.sym[n.ident] = &symbol{kind: typeSym, typ: t}
break
}
if t.node == nil {
t.node = n
}
t = sym.typ
if t.incomplete && t.node != n {
m := t.method
if t, err = nodeType(interp, sc, t.node); err != nil {
return nil, err
}
} else {
t.incomplete = true
sc.sym[n.ident] = &symbol{kind: typeSym, typ: t}
t.method = m
sym.typ = t
}
if t.node == nil {
t.node = n
}
case indexExpr:
@@ -477,9 +529,31 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
case selectorExpr:
// Resolve the left part of selector, then lookup the right part on it
var lt *itype
if lt, err = nodeType(interp, sc, n.child[0]); err != nil {
// If we are in a list of func parameters, and we are a selector on a binPkgT, but
// one of the other parameters has the same name as the pkg name, in the list of
// symbols we would find the other parameter instead of the pkg because it comes
// first when looking up in the stack of scopes. So in that case we force the
// lookup directly in the root scope to shortcircuit that issue.
var localScope *scope
localScope = sc
if n.anc != nil && len(n.anc.child) > 1 && n.anc.child[1] == n &&
// This check is weaker than what we actually want to know, i.e. whether
// n.anc.child[0] is a variable, but it seems at this point in the run we have no
// way of knowing that yet (typ is nil, so there's no typ.cat yet).
n.anc.child[0].kind == identExpr {
for {
if localScope.level == 0 {
break
}
localScope = localScope.anc
}
}
if lt, err = nodeType(interp, localScope, n.child[0]); err != nil {
return nil, err
}
if lt.incomplete {
t.incomplete = true
break
@@ -594,7 +668,7 @@ func (interp *Interpreter) isBuiltinCall(n *node) bool {
return s != nil && s.kind == bltnSym
}
// struct name returns the name of a struct type
// struct name returns the name of a struct type.
func typeName(n *node) string {
if n.anc.kind == typeSpec {
return n.anc.child[0].ident
@@ -602,7 +676,7 @@ func typeName(n *node) string {
return ""
}
// fieldName returns an implicit struct field name according to node kind
// fieldName returns an implicit struct field name according to node kind.
func fieldName(n *node) string {
switch n.kind {
case selectorExpr:
@@ -617,6 +691,7 @@ func fieldName(n *node) string {
}
var zeroValues [maxT]reflect.Value
var okFor [aMax][maxT]bool
func init() {
zeroValues[boolT] = reflect.ValueOf(false)
@@ -637,6 +712,77 @@ func init() {
zeroValues[uint32T] = reflect.ValueOf(uint32(0))
zeroValues[uint64T] = reflect.ValueOf(uint64(0))
zeroValues[uintptrT] = reflect.ValueOf(uintptr(0))
// Calculate the action -> type allowances
var (
okForEq [maxT]bool
okForCmp [maxT]bool
okForAdd [maxT]bool
okForAnd [maxT]bool
okForBool [maxT]bool
okForArith [maxT]bool
)
for cat := tcat(0); cat < maxT; cat++ {
if (cat >= intT && cat <= int64T) || (cat >= uintT && cat <= uintptrT) {
okForEq[cat] = true
okForCmp[cat] = true
okForAdd[cat] = true
okForAnd[cat] = true
okForArith[cat] = true
}
if cat == float32T || cat == float64T {
okForEq[cat] = true
okForCmp[cat] = true
okForAdd[cat] = true
okForArith[cat] = true
}
if cat == complex64T || cat == complex128T {
okForEq[cat] = true
okForAdd[cat] = true
okForArith[cat] = true
}
}
okForAdd[stringT] = true
okForBool[boolT] = true
okForEq[nilT] = true
okForEq[ptrT] = true
okForEq[interfaceT] = true
okForEq[errorT] = true
okForEq[chanT] = true
okForEq[stringT] = true
okForEq[boolT] = true
okForEq[mapT] = true // nil only
okForEq[funcT] = true // nil only
okForEq[arrayT] = true // array: only if element type is comparable slice: nil only
okForEq[structT] = true // only if all struct fields are comparable
okForCmp[stringT] = true
okFor[aAdd] = okForAdd
okFor[aAnd] = okForAnd
okFor[aLand] = okForBool
okFor[aAndNot] = okForAnd
okFor[aQuo] = okForArith
okFor[aEqual] = okForEq
okFor[aGreaterEqual] = okForCmp
okFor[aGreater] = okForCmp
okFor[aLowerEqual] = okForCmp
okFor[aLower] = okForCmp
okFor[aRem] = okForAnd
okFor[aMul] = okForArith
okFor[aNotEqual] = okForEq
okFor[aOr] = okForAnd
okFor[aLor] = okForBool
okFor[aSub] = okForArith
okFor[aXor] = okForAnd
okFor[aShl] = okForAnd
okFor[aShr] = okForAnd
okFor[aNeg] = okForArith
okFor[aNot] = okForBool
okFor[aPos] = okForArith
}
// Finalize returns a type pointer and error. It reparses a type from the
@@ -680,7 +826,7 @@ func (t *itype) referTo(name string, seen map[*itype]bool) bool {
}
seen[t] = true
switch t.cat {
case aliasT, arrayT, chanT, ptrT:
case aliasT, arrayT, chanT, chanRecvT, chanSendT, ptrT:
return t.val.referTo(name, seen)
case funcT:
for _, a := range t.arg {
@@ -756,7 +902,7 @@ func isComplete(t *itype, visited map[string]bool) bool {
visited[name] = true
}
switch t.cat {
case aliasT, arrayT, chanT, ptrT:
case aliasT, arrayT, chanT, chanRecvT, chanSendT, ptrT:
return isComplete(t.val, visited)
case funcT:
complete := true
@@ -781,6 +927,12 @@ func isComplete(t *itype, visited map[string]bool) bool {
return true
}
// comparable returns true if the type is comparable.
func (t *itype) comparable() bool {
typ := t.TypeOf()
return t.cat == nilT || typ != nil && typ.Comparable()
}
// Equals returns true if the given type is identical to the receiver one.
func (t *itype) equals(o *itype) bool {
switch ti, oi := isInterface(t), isInterface(o); {
@@ -852,7 +1004,7 @@ func (t *itype) methods() methodSet {
return res
}
// id returns a unique type identificator string
// id returns a unique type identificator string.
func (t *itype) id() (res string) {
if t.name != "" {
return t.path + "." + t.name
@@ -861,7 +1013,11 @@ func (t *itype) id() (res string) {
case arrayT:
res = "[" + strconv.Itoa(t.size) + "]" + t.val.id()
case chanT:
res = "<-" + t.val.id()
res = "chan " + t.val.id()
case chanSendT:
res = "chan<- " + t.val.id()
case chanRecvT:
res = "<-chan " + t.val.id()
case funcT:
res = "func("
for _, t := range t.arg {
@@ -894,7 +1050,7 @@ func (t *itype) id() (res string) {
return res
}
// zero instantiates and return a zero value object for the given type during execution
// zero instantiates and return a zero value object for the given type during execution.
func (t *itype) zero() (v reflect.Value, err error) {
if t, err = t.finalize(); err != nil {
return v, err
@@ -915,7 +1071,7 @@ func (t *itype) zero() (v reflect.Value, err error) {
return v, err
}
// fieldIndex returns the field index from name in a struct, or -1 if not found
// fieldIndex returns the field index from name in a struct, or -1 if not found.
func (t *itype) fieldIndex(name string) int {
switch t.cat {
case aliasT, ptrT:
@@ -929,7 +1085,7 @@ func (t *itype) fieldIndex(name string) int {
return -1
}
// fieldSeq returns the field type from the list of field indexes
// fieldSeq returns the field type from the list of field indexes.
func (t *itype) fieldSeq(seq []int) *itype {
ft := t
for _, i := range seq {
@@ -941,7 +1097,7 @@ func (t *itype) fieldSeq(seq []int) *itype {
return ft
}
// lookupField returns a list of indices, i.e. a path to access a field in a struct object
// lookupField returns a list of indices, i.e. a path to access a field in a struct object.
func (t *itype) lookupField(name string) []int {
switch t.cat {
case aliasT, ptrT:
@@ -963,7 +1119,7 @@ func (t *itype) lookupField(name string) []int {
return nil
}
// lookupBinField returns a structfield and a path to access an embedded binary field in a struct object
// lookupBinField returns a structfield and a path to access an embedded binary field in a struct object.
func (t *itype) lookupBinField(name string) (s reflect.StructField, index []int, ok bool) {
if t.cat == ptrT {
return t.val.lookupBinField(name)
@@ -1063,8 +1219,9 @@ func exportName(s string) string {
}
var interf = reflect.TypeOf((*interface{})(nil)).Elem()
var constVal = reflect.TypeOf((*constant.Value)(nil)).Elem()
// RefType returns a reflect.Type representation from an interpereter type.
// RefType returns a reflect.Type representation from an interpreter type.
// In simple cases, reflect types are directly mapped from the interpreter
// counterpart.
// For recursive named struct or interfaces, as reflect does not permit to
@@ -1088,6 +1245,10 @@ func (t *itype) refType(defined map[string]*itype, wrapRecursive bool) reflect.T
// a node can still point to a previous copy.
st.typ.recursive = st.typ.recursive || st.typ.isRecursive()
recursive = st.typ.isRecursive()
// It is possible that t.recursive is not inline with st.typ.recursive
// which will break recursion detection. Set it here to make sure it
// is correct.
t.recursive = recursive
}
}
if wrapRecursive && t.recursive {
@@ -1099,9 +1260,11 @@ func (t *itype) refType(defined map[string]*itype, wrapRecursive bool) reflect.T
if defined[name] != nil && defined[name].rtype != nil {
return defined[name].rtype
}
if t.val != nil && defined[t.val.path+"/"+t.val.name] != nil && t.val.rtype == nil {
if t.val != nil && t.val.cat == structT && t.val.rtype == nil && hasRecursiveStruct(t.val, copyDefined(defined)) {
// Replace reference to self (direct or indirect) by an interface{} to handle
// recursive types with reflect.
typ := *t.val
t.val = &typ
t.val.rtype = interf
recursive = true
}
@@ -1116,21 +1279,27 @@ func (t *itype) refType(defined map[string]*itype, wrapRecursive bool) reflect.T
}
case chanT:
t.rtype = reflect.ChanOf(reflect.BothDir, t.val.refType(defined, wrapRecursive))
case chanRecvT:
t.rtype = reflect.ChanOf(reflect.RecvDir, t.val.refType(defined, wrapRecursive))
case chanSendT:
t.rtype = reflect.ChanOf(reflect.SendDir, t.val.refType(defined, wrapRecursive))
case errorT:
t.rtype = reflect.TypeOf(new(error)).Elem()
case funcT:
if t.name != "" {
defined[name] = t
}
variadic := false
in := make([]reflect.Type, len(t.arg))
out := make([]reflect.Type, len(t.ret))
for i, v := range t.arg {
in[i] = v.refType(defined, true)
variadic = v.cat == variadicT
}
for i, v := range t.ret {
out[i] = v.refType(defined, true)
}
t.rtype = reflect.FuncOf(in, out, false)
t.rtype = reflect.FuncOf(in, out, variadic)
case interfaceT:
t.rtype = interf
case mapT:
@@ -1198,6 +1367,134 @@ func (t *itype) implements(it *itype) bool {
return t.methods().contains(it.methods())
}
func copyDefined(m map[string]*itype) map[string]*itype {
n := make(map[string]*itype, len(m))
for k, v := range m {
n[k] = v
}
return n
}
// hasRecursiveStruct determines if a struct is a recursion or a recursion
// intermediate. A recursion intermediate is a struct that contains a recursive
// struct.
func hasRecursiveStruct(t *itype, defined map[string]*itype) bool {
if len(defined) == 0 {
return false
}
typ := t
for typ != nil {
if typ.cat != structT {
typ = typ.val
continue
}
if defined[typ.path+"/"+typ.name] != nil {
return true
}
defined[typ.path+"/"+typ.name] = typ
for _, f := range typ.field {
if hasRecursiveStruct(f.typ, defined) {
return true
}
}
return false
}
return false
}
var errType = reflect.TypeOf((*error)(nil)).Elem()
func catOf(t reflect.Type) tcat {
if t == nil {
return nilT
}
if t == errType {
return errorT
}
switch t.Kind() {
case reflect.Bool:
return boolT
case reflect.Int:
return intT
case reflect.Int8:
return int8T
case reflect.Int16:
return int16T
case reflect.Int32:
return int32T
case reflect.Int64:
return int64T
case reflect.Uint:
return uintT
case reflect.Uint8:
return uint8T
case reflect.Uint16:
return uint16T
case reflect.Uint32:
return uint32T
case reflect.Uint64:
return uint64T
case reflect.Uintptr:
return uintptrT
case reflect.Float32:
return float32T
case reflect.Float64:
return float64T
case reflect.Complex64:
return complex64T
case reflect.Complex128:
return complex128T
case reflect.Array, reflect.Slice:
return arrayT
case reflect.Chan:
return chanT
case reflect.Func:
return funcT
case reflect.Interface:
return interfaceT
case reflect.Map:
return mapT
case reflect.Ptr:
return ptrT
case reflect.String:
return stringT
case reflect.Struct:
return structT
case reflect.UnsafePointer:
return uintptrT
}
return nilT
}
func catOfConst(v reflect.Value) tcat {
c, ok := v.Interface().(constant.Value)
if !ok {
return nilT
}
switch c.Kind() {
case constant.Int:
return intT
case constant.Float:
return float64T
case constant.Complex:
return complex128T
default:
return nilT
}
}
func constToInt(c constant.Value) int {
if constant.BitLen(c) > 64 {
panic(fmt.Sprintf("constant %s overflows int64", c.ExactString()))
}
i, _ := constant.Int64Val(c)
return int(i)
}
func defRecvType(n *node) *itype {
if n.kind != funcDecl || len(n.child[0].child) == 0 {
return nil
@@ -1216,12 +1513,20 @@ func isShiftNode(n *node) bool {
return false
}
func isComparisonNode(n *node) bool {
switch n.action {
case aEqual, aNotEqual, aGreater, aGreaterEqual, aLower, aLowerEqual:
return true
}
return false
}
// chanElement returns the channel element type.
func chanElement(t *itype) *itype {
switch t.cat {
case aliasT:
return chanElement(t.val)
case chanT:
case chanT, chanSendT, chanRecvT:
return t.val
case valueT:
return &itype{cat: valueT, rtype: t.rtype.Elem(), node: t.node, scope: t.scope}
@@ -1231,14 +1536,22 @@ func chanElement(t *itype) *itype {
func isBool(t *itype) bool { return t.TypeOf().Kind() == reflect.Bool }
func isChan(t *itype) bool { return t.TypeOf().Kind() == reflect.Chan }
func isMap(t *itype) bool { return t.TypeOf().Kind() == reflect.Map }
func isSendChan(t *itype) bool {
rt := t.TypeOf()
return rt.Kind() == reflect.Chan && rt.ChanDir() == reflect.SendDir
}
func isMap(t *itype) bool { return t.TypeOf().Kind() == reflect.Map }
func isArray(t *itype) bool {
k := t.TypeOf().Kind()
return k == reflect.Array || k == reflect.Slice
}
func isInterfaceSrc(t *itype) bool {
return t.cat == interfaceT || (t.cat == aliasT && isInterfaceSrc(t.val))
}
func isInterface(t *itype) bool {
return isInterfaceSrc(t) || t.TypeOf().Kind() == reflect.Interface
return isInterfaceSrc(t) || t.TypeOf() != nil && t.TypeOf().Kind() == reflect.Interface
}
func isStruct(t *itype) bool {
@@ -1311,5 +1624,8 @@ func isByteArray(t reflect.Type) bool {
func isFloat32(t reflect.Type) bool { return t != nil && t.Kind() == reflect.Float32 }
func isFloat64(t reflect.Type) bool { return t != nil && t.Kind() == reflect.Float64 }
func isNumber(t reflect.Type) bool { return isInt(t) || isFloat(t) || isComplex(t) }
func isString(t reflect.Type) bool { return t != nil && t.Kind() == reflect.String }
func isNumber(t reflect.Type) bool {
return isInt(t) || isFloat(t) || isComplex(t) || isConstantValue(t)
}
func isString(t reflect.Type) bool { return t != nil && t.Kind() == reflect.String }
func isConstantValue(t reflect.Type) bool { return t != nil && t.Implements(constVal) }

View File

@@ -1,6 +1,7 @@
package interp
import (
"go/constant"
"reflect"
)
@@ -96,6 +97,7 @@ func genValueAs(n *node, t reflect.Type) func(*frame) reflect.Value {
func genValue(n *node) func(*frame) reflect.Value {
switch n.kind {
case basicLit:
convertConstantValue(n)
v := n.rval
if !v.IsValid() {
v = reflect.New(interf).Elem()
@@ -111,6 +113,7 @@ func genValue(n *node) func(*frame) reflect.Value {
return func(f *frame) reflect.Value { return v }
default:
if n.rval.IsValid() {
convertConstantValue(n)
v := n.rval
return func(f *frame) reflect.Value { return v }
}
@@ -179,16 +182,6 @@ func genValueInterfaceArray(n *node) func(*frame) reflect.Value {
}
}
func genValueInterfacePtr(n *node) func(*frame) reflect.Value {
value := genValue(n)
return func(f *frame) reflect.Value {
v := reflect.New(interf).Elem()
v.Set(value(f))
return v.Addr()
}
}
func genValueInterface(n *node) func(*frame) reflect.Value {
value := genValue(n)
@@ -208,18 +201,6 @@ func genValueInterface(n *node) func(*frame) reflect.Value {
}
}
func genValueDerefInterfacePtr(n *node) func(*frame) reflect.Value {
value := genValue(n)
return func(f *frame) reflect.Value {
v := value(f)
if v.IsZero() {
return v
}
return v.Elem().Elem()
}
}
func zeroInterfaceValue() reflect.Value {
n := &node{kind: basicLit, typ: &itype{cat: nilT, untyped: true}}
v := reflect.New(interf).Elem()
@@ -266,6 +247,63 @@ func genValueNode(n *node) func(*frame) reflect.Value {
}
}
func genValueRecursiveInterface(n *node, t reflect.Type) func(*frame) reflect.Value {
value := genValue(n)
return func(f *frame) reflect.Value {
vv := value(f)
v := reflect.New(t).Elem()
toRecursive(v, vv)
return v
}
}
func toRecursive(dest, src reflect.Value) {
if !src.IsValid() {
return
}
switch dest.Kind() {
case reflect.Map:
v := reflect.MakeMapWithSize(dest.Type(), src.Len())
for _, kv := range src.MapKeys() {
vv := reflect.New(dest.Type().Elem()).Elem()
toRecursive(vv, src.MapIndex(kv))
vv.SetMapIndex(kv, vv)
}
dest.Set(v)
case reflect.Slice:
l := src.Len()
v := reflect.MakeSlice(dest.Type(), l, l)
for i := 0; i < l; i++ {
toRecursive(v.Index(i), src.Index(i))
}
dest.Set(v)
case reflect.Ptr:
v := reflect.New(dest.Type().Elem()).Elem()
s := src
if s.Elem().Kind() != reflect.Struct { // In the case of *interface{}, we want *struct{}
s = s.Elem()
}
toRecursive(v, s)
dest.Set(v.Addr())
default:
dest.Set(src)
}
}
func genValueRecursiveInterfacePtrValue(n *node) func(*frame) reflect.Value {
value := genValue(n)
return func(f *frame) reflect.Value {
v := value(f)
if v.IsZero() {
return v
}
return v.Elem().Elem()
}
}
func vInt(v reflect.Value) (i int64) {
switch v.Type().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
@@ -277,6 +315,10 @@ func vInt(v reflect.Value) (i int64) {
case reflect.Complex64, reflect.Complex128:
i = int64(real(v.Complex()))
}
if v.Type().Implements(constVal) {
c := v.Interface().(constant.Value)
i, _ = constant.Int64Val(constant.ToInt(c))
}
return
}
@@ -291,6 +333,11 @@ func vUint(v reflect.Value) (i uint64) {
case reflect.Complex64, reflect.Complex128:
i = uint64(real(v.Complex()))
}
if v.Type().Implements(constVal) {
c := v.Interface().(constant.Value)
iv, _ := constant.Int64Val(constant.ToInt(c))
i = uint64(iv)
}
return
}
@@ -305,6 +352,13 @@ func vComplex(v reflect.Value) (c complex128) {
case reflect.Complex64, reflect.Complex128:
c = v.Complex()
}
if v.Type().Implements(constVal) {
con := v.Interface().(constant.Value)
con = constant.ToComplex(con)
rel, _ := constant.Float64Val(constant.Real(con))
img, _ := constant.Float64Val(constant.Imag(con))
c = complex(rel, img)
}
return
}
@@ -319,6 +373,17 @@ func vFloat(v reflect.Value) (i float64) {
case reflect.Complex64, reflect.Complex128:
i = real(v.Complex())
}
if v.Type().Implements(constVal) {
c := v.Interface().(constant.Value)
i, _ = constant.Float64Val(constant.ToFloat(c))
}
return
}
func vConstantValue(v reflect.Value) (c constant.Value) {
if v.Type().Implements(constVal) {
c = v.Interface().(constant.Value)
}
return
}

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