Compare commits

...

54 Commits

Author SHA1 Message Date
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
Nicholas Wiersma
8365f687e7 fix: composite map assign (#683)
* fix: composite map assign

* reword comment

Co-authored-by: Marc Vertes <mvertes@free.fr>
2020-06-10 12:29:09 +02:00
Nicholas Wiersma
36836cd4f2 fix: return correct package name for parsed package (#679)
Co-authored-by: Marc Vertes <mvertes@free.fr>
2020-06-10 12:17:10 +02:00
Marc Vertes
82b499a1c8 fix: detect default comm clause in select from AST (#678)
* fix: detect default comm clause in select from AST

The heuristic to distinguish a default comm clause was too weak.
Make it robust by using AST.

Fixes #646.

* rename test to avoid conflict
2020-06-10 12:06:59 +02:00
Marc Vertes
0ef7f8fb80 fix: perform send channel action for select (#665)
* fix: perform send channel action for select

The CFG was wrong for select send comm clauses. If an init operation
on channel was required (like a derefence, index operation, ...) it
was skipped. The bug was invisible in case of a local var channel.

A send channel init operation consist to prepare both the data to
send (right subtree of send AST) and the channel itself (left
subtree of send AST). All channel init operation must be performed
prior to call select.

Fixes #647.

* doc: fix comment

* invert test to continue early

please note that this also changes the logic a little bit, since the
line that was:

if pn != nil {

now implictly becomes:

if an != nil && pn != nil {

(which I think is actually more correct).

* explicit chaining of init actions in select

* explicit chaining of init actions in select

Co-authored-by: mpl <mathieu.lonjaret@gmail.com>
2020-06-10 11:37:06 +02:00
Marc Vertes
c1f5005b2a fix: finish support of type assertions which was incomplete (#657)
* fix: finish support of type assertions which was incomplete

TypeAssert was optimistically returning ok without verifying that
value could be converted to the required interface (in case of
type assert of an interface type), or not checking the type in
all conditions. There is now a working implements method for itype.

Fixes #640.

* style: appease lint

* fix: remove useless code block

* doc: improve comments

* avoid test conflict
2020-06-10 11:21:16 +02:00
Nicholas Wiersma
def57d57c2 fix: dont assign receiver on indexExpr 2020-06-03 19:10:04 +02:00
mpl
74479d70e6 Travis: apply timeout multiplier on CI 2020-06-03 17:50:04 +02:00
Nicholas Wiersma
f1cff308e6 fix: handle printf changing nil type 2020-06-03 17:36:04 +02:00
mpl
6f878052f8 interp: detect local redeclaration (#673)
* interp: detect local redecleration

Fixes #666

* make make check happy

* fix typo in filenames, add them to the exclusion list

Co-authored-by: Marc Vertes <mvertes@free.fr>
2020-06-03 17:21:00 +02:00
Nicholas Wiersma
eb25c84797 fix: handle call ellipsis 2020-06-03 16:42:04 +02:00
Nicholas Wiersma
2de0c80d38 fix: handle range on chan function return 2020-06-03 16:30:05 +02:00
Nicholas Wiersma
151a856bf2 fix: composite literal child types for call expressions 2020-06-03 09:44:03 +02:00
Nicholas Wiersma
d5217f7db4 fix: handle nil interface conversion 2020-06-03 00:32:04 +02:00
Nicholas Wiersma
01e4cdea70 fix: recursive struct function call 2020-06-03 00:18:04 +02:00
Nicholas Wiersma
3c88542180 fix: handle empty interface conversion 2020-05-29 13:58:04 +02:00
Nicholas Wiersma
56d88ef89d fix: nil recover case 2020-05-29 13:28:03 +02:00
Nicholas Wiersma
5d78c8ae27 fix: compose recursion 2020-05-28 08:52:03 +02:00
Nicholas Wiersma
184623d81f Fix import source symbol package 2020-05-27 17:16:03 +02:00
mpl
5d56bac8d0 interp: extend dot debugging 2020-05-26 22:38:03 +02:00
Marc Vertes
4a068ea452 fix: hande empty clause in switch if statement (#635)
The CFG was wrong in presence of an empty last clause.

Fixes #634.
2020-05-20 21:57:24 +02:00
Marc Vertes
8605c238ef fix: use interface wrappers to expose interface values to runtime (#643)
* fix: use interface wrappers to expose interface values to runtime

If a value is assigned to, or returned as, a binary interface,
then use the interface wrapper generator to convert the value
accordingly.

Fixes #630.

* test: rename NewFoo in Foo

* test: rename NewFoo in Foo

* fix: improve branch flow to reduce indentation
2020-05-20 21:46:14 +02:00
Marc Vertes
4f39eaf893 fix: correct iterator in map range on binary values (#644)
* fix: correct iterator in map range on binary values

The map range iterator was not initialized correctly for values
originating from runtime.

Fixes #641.

* test: deterministic output for map28.go
2020-05-20 21:37:48 +02:00
Marc Vertes
4f8e1de267 fix: check the number of argument passed and returned to functions 2020-05-19 14:56:04 +02:00
Marc Vertes
6f4643ff19 fix: compute numeral constant on typed objects 2020-05-19 14:38:03 +02:00
Ozan Hacıbekiroğlu
bb1be9e6e1 add repl input bounds check (#638)
* add repl input bounds check

* use bytes.Equal instead of Compare

* fix: use strings.HasPrefix to perform boundcheck on REPL input

This allows to keep most of the code unchanged and more readable.

Co-authored-by: Marc Vertes <mvertes@free.fr>
2020-05-18 18:53:39 +02:00
Marc Vertes
cdf4622421 fix: assign an element in a map from runtime 2020-05-05 22:46:04 +02:00
Marc Vertes
b9720d15e1 fix: improve setting results in multiple output functions 2020-05-05 22:34:03 +02:00
Marc Vertes
7fab75fbe4 fix: correct assign from function returning an interface value (#625) 2020-05-05 13:54:36 +02:00
Marc Vertes
7eac6955b3 feature: pre-import used packages in REPL mode. 2020-05-04 16:18:05 +02:00
Marc Vertes
7070367d21 fix: handle constant declarations inside function scopes 2020-05-03 18:56:04 +02:00
Marc Vertes
22dfc8b10a fix: handle set of variadic arg list for a ... value 2020-05-03 18:44:04 +02:00
Marc Vertes
7fba3fe580 fix: improve generation of interface wrapper 2020-05-03 18:32:04 +02:00
Marc Vertes
ff36ec58b1 fix: improve switch and type switch statement processing 2020-05-03 18:20:04 +02:00
Marc Vertes
f6c8b8b14f fix: check that a function returns the correct number of values 2020-05-03 18:08:03 +02:00
Marc Vertes
e12c8b72c4 fix: store the result of a bin function call in frame 2020-05-03 17:56:04 +02:00
Marc Vertes
e4acba031d fix: improving handling of functions returning interfaces 2020-05-03 17:46:03 +02:00
Marc Vertes
7d56fb067e fix: improve setting of interface objects 2020-04-30 17:44:04 +02:00
Marc Vertes
1feece61ce fix: improve handling of function return values 2020-04-30 10:32:04 +02:00
Marc Vertes
01e2e4600e fix: handle default comm clause in select 2020-04-25 17:40:08 +02:00
Marc Vertes
92eebbade2 fix: handle function output value assigned to interface var 2020-04-25 17:28:04 +02:00
Marc Vertes
a6389aca5e fix: improve handling of nil 2020-04-25 17:16:03 +02:00
Marc Vertes
de8cb7dc3b fix: handle type conversion from pre-compiled types. 2020-04-23 19:20:04 +02:00
Marc Vertes
18b843646c fix: parsing of recursive interface types 2020-04-22 19:26:05 +02:00
Marc Vertes
71f730b3d7 fix: handle forward declaration for aliased type 2020-04-22 10:50:05 +02:00
Marc Vertes
7b2d91bcb5 fix: handling values in map of interfaces 2020-04-22 10:34:05 +02:00
Marc Vertes
3ed4ec3f6f fix: correct type assertion for bin func types (#588) 2020-04-20 23:01:01 +02:00
Marc Vertes
5e142fdedd fix: do not attempt to copy data in empty frame at func call 2020-04-18 18:36:04 +02:00
Marc Vertes
94d44e7265 fix: do not convert literal float to int at parse 2020-04-17 17:54:03 +02:00
Marc Vertes
56925e6fea fix: correct branch control flow graph for parenthesis expressions (#583) 2020-04-17 14:40:56 +02:00
278 changed files with 126346 additions and 123113 deletions

View File

@@ -26,6 +26,7 @@ matrix:
env:
global:
- GO111MODULE=on
- CI=1
go_import_path: github.com/containous/yaegi
@@ -39,7 +40,7 @@ install:
- go mod download
before_script:
- rm -f interp/op.go interp/interp_test.go
- rm -f interp/op.go
- make generate
- git update-index -q --refresh
- CHANGED=$(git diff-index --name-only HEAD --)

9
_test/add0.go Normal file
View File

@@ -0,0 +1,9 @@
package main
func main() {
var a interface{} = 2 + 5
println(a.(int))
}
// Output:
// 7

10
_test/add1.go Normal file
View File

@@ -0,0 +1,10 @@
package main
func main() {
b := 2
var a interface{} = 5 + b
println(a.(int))
}
// Output:
// 7

12
_test/add2.go Normal file
View File

@@ -0,0 +1,12 @@
package main
type iface interface{}
func main() {
b := 2
var a iface = 5 + b
println(a.(int))
}
// Output:
// 7

14
_test/append0.go Normal file
View File

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

10
_test/assign11.go Normal file
View File

@@ -0,0 +1,10 @@
package main
import "fmt"
func main() {
_, _, _ = fmt.Println("test")
}
// Error:
// 6:2: assignment mismatch: 3 variables but fmt.Println returns 2 values

11
_test/assign12.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import "fmt"
func main() {
a, b, c := fmt.Println("test")
println(a, b, c)
}
// Error:
// 6:2: assignment mismatch: 3 variables but fmt.Println returns 2 values

3
_test/baz-bat/baz-bat.go Normal file
View File

@@ -0,0 +1,3 @@
package baz
var Name = "baz-bat"

18
_test/bin4.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"strings"
)
func Bar(s string) bool {
a := strings.HasPrefix("fas", "f")
b := strings.HasPrefix("aaaaa", "a")
a_and_b := strings.HasPrefix("fas", "f") && strings.HasPrefix("aaaaa", "a")
fmt.Println(a, b, a && b, a_and_b)
return a && b
}
func main() {
println(Bar("kung"))
}

15
_test/bin5.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import (
"fmt"
"net"
)
func main() {
addr := net.TCPAddr{IP: net.IPv4(1, 1, 1, 1), Port: 80}
var s fmt.Stringer = &addr
fmt.Println(s.String())
}
// Output:
// 1.1.1.1:80

13
_test/cap0.go Normal file
View File

@@ -0,0 +1,13 @@
package main
func f(a []int) interface{} {
return cap(a)
}
func main() {
a := []int{1, 2}
println(f(a).(int))
}
// Output:
// 2

9
_test/comp1.go Normal file
View File

@@ -0,0 +1,9 @@
package main
func main() {
var a interface{} = 1 < 2
println(a.(bool))
}
// Output:
// true

13
_test/complex4.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import "fmt"
func f(a, b float64) interface{} { return complex(a, b) }
func main() {
a := f(3, 2)
fmt.Println(a.(complex128))
}
// Output:
// (3+2i)

14
_test/composite10.go Normal file
View File

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

14
_test/composite9.go Normal file
View File

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

13
_test/const11.go Normal file
View File

@@ -0,0 +1,13 @@
package main
func main() {
const (
x = 2 * iota
dim
)
var t [dim * 2]int
println(t[0], len(t))
}
// Output:
// 0 4

17
_test/const12.go Normal file
View File

@@ -0,0 +1,17 @@
package main
type Kind int
const (
None Kind = 0
Left Kind = 1 << iota
Right
Both Kind = Left | Right
)
func main() {
println(None, Left, Right, Both)
}
// Output:
// 0 2 4 6

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

16
_test/copy2.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import "fmt"
func f(a, b []int) interface{} { return copy(a, b) }
func main() {
a := []int{10, 20, 30}
b := [4]int{}
c := b[:]
r := f(c, a)
fmt.Println(r.(int))
}
// Output:
// 3

12
_test/fun12.go Normal file
View File

@@ -0,0 +1,12 @@
package main
func use(interface{}) {}
func main() {
z := map[string]interface{}{"a": 5}
use(z)
println("bye")
}
// Output:
// bye

19
_test/fun13.go Normal file
View File

@@ -0,0 +1,19 @@
package main
import "fmt"
type T struct{}
func newT() (T, error) { return T{}, nil }
func main() {
var (
i interface{}
err error
)
i, err = newT()
fmt.Println(i, err)
}
// Output:
// {} <nil>

13
_test/fun14.go Normal file
View File

@@ -0,0 +1,13 @@
package main
func f() (bool, int) { return true, 2 }
func g() (bool, int) { return f() }
func main() {
b, i := g()
println(b, i)
}
// Output:
// true 2

11
_test/fun15.go Normal file
View File

@@ -0,0 +1,11 @@
package main
func f1(a int) interface{} { return a + 1 }
func main() {
c := f1(3)
println(c.(int))
}
// Output:
// 4

13
_test/fun16.go Normal file
View File

@@ -0,0 +1,13 @@
package main
func f1(a int) int { return a + 1 }
func f2(a int) interface{} { return f1(a) }
func main() {
c := f2(3)
println(c.(int))
}
// Output:
// 4

13
_test/fun17.go Normal file
View File

@@ -0,0 +1,13 @@
package main
func f1(a int) interface{} { return a + 1 }
func f2(a int) interface{} { return f1(a) }
func main() {
c := f2(3)
println(c.(int))
}
// Output:
// 4

12
_test/fun18.go Normal file
View File

@@ -0,0 +1,12 @@
package main
var m = map[string]int{"foo": 1, "bar": 2}
func f(s string) interface{} { return m[s] }
func main() {
println(f("foo").(int))
}
// Output:
// 1

17
_test/fun19.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import (
"fmt"
)
func foo() ([]string, error) {
return nil, fmt.Errorf("bar")
}
func main() {
a, b := foo()
fmt.Println(a, b)
}
// Output:
// [] bar

19
_test/fun20.go Normal file
View File

@@ -0,0 +1,19 @@
package main
import "fmt"
var myerr error = fmt.Errorf("bar")
func ferr() error { return myerr }
func foo() ([]string, error) {
return nil, ferr()
}
func main() {
a, b := foo()
fmt.Println(a, b)
}
// Output:
// [] bar

12
_test/fun21.go Normal file
View File

@@ -0,0 +1,12 @@
package main
func Bar() string {
return
}
func main() {
println(Bar())
}
// Error:
// 4:2: not enough arguments to return

10
_test/fun22.go Normal file
View File

@@ -0,0 +1,10 @@
package main
import "time"
func main() {
time.Date()
}
// Error:
// 6:2: not enough arguments in call to time.Date

14
_test/imag0.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
func f(c complex128) interface{} { return imag(c) }
func main() {
c := complex(3, 2)
a := f(c)
fmt.Println(a.(float64))
}
// Output:
// 2

10
_test/import9.go Normal file
View File

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

37
_test/interface36.go Normal file
View File

@@ -0,0 +1,37 @@
package main
import "fmt"
var (
t *S
_ I = t
_ J = t
)
type S struct {
Name string
}
func (s *S) F() int { return len(s.Name) }
func (s *S) G() int { return s.F() }
func (s *S) Ri() I { return s }
func (s *S) Rj() J { return s }
type J interface {
I
G() int
Rj() J
}
type I interface {
F() int
Ri() I
}
func main() {
var j J
fmt.Println(j)
}
// Output:
// <nil>

23
_test/interface37.go Normal file
View File

@@ -0,0 +1,23 @@
package main
type I interface {
A() string
B() string
}
type s struct{}
func NewS() (I, error) {
return &s{}, nil
}
func (c *s) A() string { return "a" }
func (c *s) B() string { return "b" }
func main() {
s, _ := NewS()
println(s.A())
}
// Output:
// a

19
_test/interface38.go Normal file
View File

@@ -0,0 +1,19 @@
package main
import "fmt"
type foo struct {
bar string
}
func (f foo) String() string {
return "Hello from " + f.bar
}
func main() {
var f fmt.Stringer = foo{bar: "bar"}
fmt.Println(f)
}
// Output:
// Hello from bar

19
_test/interface39.go Normal file
View File

@@ -0,0 +1,19 @@
package main
import "fmt"
type foo struct {
bar string
}
func (f *foo) String() string {
return "Hello from " + f.bar
}
func main() {
var f fmt.Stringer = &foo{bar: "bar"}
fmt.Println(f)
}
// Output:
// Hello from bar

23
_test/interface40.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import "fmt"
type foo struct {
bar string
}
func (f foo) String() string {
return "Hello from " + f.bar
}
func Foo(s string) fmt.Stringer {
return foo{s}
}
func main() {
f := Foo("bar")
fmt.Println(f)
}
// Output:
// Hello from bar

23
_test/interface41.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import "fmt"
type foo struct {
bar string
}
func (f *foo) String() string {
return "Hello from " + f.bar
}
func Foo(s string) fmt.Stringer {
return &foo{s}
}
func main() {
f := Foo("bar")
fmt.Println(f)
}
// Output:
// Hello from bar

12
_test/interface42.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import "fmt"
func main() {
v := interface{}(0)
fmt.Println(v)
}
// Output:
// 0

12
_test/interface43.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import "fmt"
func main() {
v := interface{}(nil)
fmt.Println(v)
}
// Output:
// <nil>

13
_test/len0.go Normal file
View File

@@ -0,0 +1,13 @@
package main
func f(a []int) interface{} {
return len(a)
}
func main() {
a := []int{1, 2}
println(f(a).(int))
}
// Output:
// 2

13
_test/make0.go Normal file
View File

@@ -0,0 +1,13 @@
package main
func f() interface{} {
return make([]int, 2)
}
func main() {
a := f()
println(len(a.([]int)))
}
// Output:
// 2

15
_test/make1.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import "fmt"
func f() interface{} {
return make(map[int]int)
}
func main() {
a, ok := f().(map[int]int)
fmt.Println(a, ok)
}
// Output:
// map[] true

18
_test/map24.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"encoding/json"
"fmt"
)
func main() {
jb := []byte(`{"property": "test"}`)
params := map[string]interface{}{"foo": 1}
if err := json.Unmarshal(jb, &params); err != nil {
panic("marshal failed.")
}
fmt.Println(params["foo"], params["property"])
}
// Output:
// 1 test

26
_test/map25.go Normal file
View File

@@ -0,0 +1,26 @@
package main
import (
"encoding/json"
"strconv"
)
func main() {
jb := []byte(`{"num": "2"}`)
params := map[string]interface{}{"foo": "1"}
if err := json.Unmarshal(jb, &params); err != nil {
panic(err)
}
sum := 0
for _, v := range params {
i, err := strconv.Atoi(v.(string))
if err != nil {
panic(err)
}
sum += i
}
println(sum)
}
// Output:
// 3

11
_test/map26.go Normal file
View File

@@ -0,0 +1,11 @@
package main
var m = map[string]int{"foo": 1, "bar": 2}
func main() {
var a interface{} = m["foo"]
println(a.(int))
}
// Output:
// 1

24
_test/map27.go Normal file
View File

@@ -0,0 +1,24 @@
package main
import (
"fmt"
"text/template"
)
type fm map[string]interface{}
type foo struct{}
func main() {
a := make(fm)
a["foo"] = &foo{}
fmt.Println(a["foo"])
b := make(template.FuncMap) // type FuncMap map[string]interface{}
b["foo"] = &foo{}
fmt.Println(b["foo"])
}
// Output:
// &{}
// &{}

21
_test/map28.go Normal file
View File

@@ -0,0 +1,21 @@
package main
import (
"net/url"
)
func main() {
value1 := url.Values{}
value1.Set("first", "v1")
value1.Set("second", "v2")
l := 0
for k, v := range value1 {
l += len(k) + len(v)
}
println(l)
}
// Output:
// 13

26
_test/map29.go Normal file
View File

@@ -0,0 +1,26 @@
package main
import (
"fmt"
"time"
)
type Item struct {
Object interface{}
Expiry time.Duration
}
func main() {
items := map[string]Item{}
items["test"] = Item{
Object: "test",
Expiry: time.Second,
}
item := items["test"]
fmt.Println(item)
}
// Output:
// {test 1s}

18
_test/method32.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
)
func main() {
var a = []func(string){bar}
b := a[0]
b("bar")
}
func bar(a string) {
fmt.Println(a)
}
// Output:
// bar

58
_test/method33.go Normal file
View File

@@ -0,0 +1,58 @@
package main
import (
"fmt"
)
type T1 struct{}
func (t1 T1) f() {
fmt.Println("T1.f()")
}
func (t1 T1) g() {
fmt.Println("T1.g()")
}
type T2 struct {
T1
}
func (t2 T2) f() {
fmt.Println("T2.f()")
}
type I interface {
f()
}
func printType(i I) {
if t1, ok := i.(T1); ok {
println("T1 ok")
t1.f()
t1.g()
}
if t2, ok := i.(T2); ok {
println("T2 ok")
t2.f()
t2.g()
}
}
func main() {
println("T1")
printType(T1{})
println("T2")
printType(T2{})
}
// Output:
// T1
// T1 ok
// T1.f()
// T1.g()
// T2
// T2 ok
// T2.f()
// T1.g()

23
_test/method34.go Normal file
View File

@@ -0,0 +1,23 @@
package main
type Root struct {
Name string
}
type One struct {
Root
}
type Hi interface {
Hello() string
}
func (r *Root) Hello() string { return "Hello " + r.Name }
func main() {
var one interface{} = &One{Root{Name: "test2"}}
println(one.(Hi).Hello())
}
// Output:
// Hello test2

13
_test/new2.go Normal file
View File

@@ -0,0 +1,13 @@
package main
func f() interface{} {
return new(int)
}
func main() {
a := f()
println(*(a.(*int)))
}
// Output:
// 0

12
_test/nil2.go Normal file
View File

@@ -0,0 +1,12 @@
package main
func test() error { return nil }
func main() {
if err := test(); nil == err {
println("err is nil")
}
}
// Output:
// err is nil

16
_test/not0.go Normal file
View File

@@ -0,0 +1,16 @@
package main
func main() {
a := 0
b := true
c := false
if b && c {
a = 1
} else {
a = -1
}
println(a)
}
// Output:
// -1

15
_test/not1.go Normal file
View File

@@ -0,0 +1,15 @@
package main
func main() {
a := 0
b := true
if (!b) {
a = 1
} else {
a = -1
}
println(a)
}
// Output:
// -1

9
_test/not2.go Normal file
View File

@@ -0,0 +1,9 @@
package main
func main() {
var b interface{} = !(1 == 2)
println(b.(bool))
}
// Output:
// true

10
_test/num0.go Normal file
View File

@@ -0,0 +1,10 @@
package main
import "fmt"
func main() {
fmt.Printf("%g\n", 1.0)
}
// Output:
// 1

View File

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

12
_test/op5.go Normal file
View File

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

21
_test/range7.go Normal file
View File

@@ -0,0 +1,21 @@
package main
import (
"fmt"
)
func someChan() <-chan struct{} {
c := make(chan struct{}, 1)
c <- struct{}{}
return c
}
func main() {
for _ = range someChan() {
fmt.Println("success")
return
}
}
// Output:
// success

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

14
_test/real0.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
func f(c complex128) interface{} { return real(c) }
func main() {
c := complex(3, 2)
a := f(c)
fmt.Println(a.(float64))
}
// Output:
// 3

15
_test/recover2.go Normal file
View File

@@ -0,0 +1,15 @@
package main
func main() {
println("hello")
var r interface{} = 1
r = recover()
if r == nil {
println("world")
}
}
// Output:
// hello
// world

22
_test/recover3.go Normal file
View File

@@ -0,0 +1,22 @@
package main
import "fmt"
func main() {
println("hello")
var r interface{} = 1
r = recover()
fmt.Printf("%v\n", r)
if r == nil {
println("world")
}
if r != nil {
println("exception")
}
}
// Output:
// hello
// <nil>
// world

25
_test/recover4.go Normal file
View File

@@ -0,0 +1,25 @@
package main
import "fmt"
func div(a, b int) (result int) {
defer func() {
r := recover()
fmt.Printf("r = %#v\n", r)
if r != nil {
result = 0
}
}()
return a / b
}
func main() {
println(div(30, 2))
}
// Output:
// r = <nil>
// 15

15
_test/redeclaration0.go Normal file
View File

@@ -0,0 +1,15 @@
package main
func main() {
type foo struct {
yolo string
}
var foo int
foo = 2
println(foo)
}
// Error:
// ../_test/redeclaration0.go:8:6: foo redeclared in this block
// previous declaration at ../_test/redeclaration0.go:4:7

12
_test/redeclaration1.go Normal file
View File

@@ -0,0 +1,12 @@
package main
func main() {
var foo string
var foo int
foo = 2
println(foo)
}
// Error:
// ../_test/redeclaration1.go:6:6: foo redeclared in this block

15
_test/redeclaration2.go Normal file
View File

@@ -0,0 +1,15 @@
package main
func main() {
var foo struct {
yolo string
}
var foo int
foo = 2
println(foo)
}
// Error:
// ../_test/redeclaration2.go:8:6: foo redeclared in this block
// previous declaration at ../_test/redeclaration2.go:4:6

13
_test/redeclaration3.go Normal file
View File

@@ -0,0 +1,13 @@
package main
func main() {
var foo int
foo = 2
type foo struct{}
var bar foo
println(bar)
}
// Error:
// ../_test/redeclaration3.go:7:7: foo redeclared in this block

14
_test/redeclaration4.go Normal file
View File

@@ -0,0 +1,14 @@
package main
func main() {
var foo struct{
yolo string
}
type foo struct{}
var bar foo
println(bar)
}
// Error:
// ../_test/redeclaration4.go:8:7: foo redeclared in this block

14
_test/redeclaration5.go Normal file
View File

@@ -0,0 +1,14 @@
package main
func main() {
type foo struct{
yolo string
}
type foo struct{}
var bar foo
println(bar)
}
// Error:
// ../_test/redeclaration5.go:8:7: foo redeclared in this block

14
_test/select10.go Normal file
View File

@@ -0,0 +1,14 @@
package main
func main() {
c := make(chan string)
select {
case <-c:
println("unexpected")
default:
}
println("bye")
}
// Output:
// bye

16
_test/select11.go Normal file
View File

@@ -0,0 +1,16 @@
package main
func main() {
c := make(chan string)
select {
case <-c:
println("unexpected")
default:
println("nothing received")
}
println("bye")
}
// Output:
// nothing received
// bye

23
_test/select12.go Normal file
View File

@@ -0,0 +1,23 @@
package main
type S struct {
q chan struct{}
}
func (s *S) Send() {
select {
case s.q <- struct{}{}:
println("sent")
default:
println("unexpected")
}
}
func main() {
s := &S{q: make(chan struct{}, 1)}
s.Send()
println("bye")
}
// Output:
// sent
// bye

16
_test/select13.go Normal file
View File

@@ -0,0 +1,16 @@
package main
func main() {
var c interface{} = int64(1)
q := make(chan struct{})
select {
case q <- struct{}{}:
println("unexpected")
default:
_ = c.(int64)
}
println("bye")
}
// Output:
// bye

View File

@@ -15,3 +15,6 @@ func main() {
_, err := c.Get("url")
println(strings.Contains(err.Error(), "unsupported protocol scheme"))
}
// Output:
// true

View File

@@ -14,4 +14,5 @@ func main() {
println(t.b)
}
// Output: true
// Output:
// true

21
_test/struct46.go Normal file
View File

@@ -0,0 +1,21 @@
package main
import "fmt"
type A struct {
B string
C D
}
type D struct {
E *A
}
func main() {
a := &A{B: "b"}
a.C = D{E: a}
fmt.Println(a.C.E.B)
}
// Output:
// b

26
_test/struct47.go Normal file
View File

@@ -0,0 +1,26 @@
package main
import "fmt"
type A struct {
B string
C D
}
func (a *A) Test() string {
return "test"
}
type D struct {
E *A
}
func main() {
a := &A{B: "b"}
d := D{E: a}
a.C = d
fmt.Println(a.C.E.Test())
}
// Output:
// test

37
_test/struct48.go Normal file
View File

@@ -0,0 +1,37 @@
package main
type List struct {
Next *List
Num int
}
func add(l *List, n int) *List {
if l == nil {
return &List{Num: n}
}
l.Next = add(l.Next, n)
return l
}
func pr(l *List) {
if l == nil {
println("")
return
}
print(l.Num)
pr(l.Next)
}
func main() {
a := add(nil, 0)
pr(a)
a = add(a, 1)
pr(a)
a = add(a, 2)
pr(a)
}
// Output:
// 0
// 01
// 012

13
_test/switch34.go Normal file
View File

@@ -0,0 +1,13 @@
package main
func main() {
var a interface{}
switch a.(type) {
case []int:
case []string:
}
println("bye")
}
// Output:
// bye

17
_test/switch35.go Normal file
View File

@@ -0,0 +1,17 @@
package main
func main() {
a := 2
switch {
case a == 1:
println(1)
case a == 2:
println(2)
default:
}
println("bye")
}
// Output:
// 2
// bye

14
_test/switch36.go Normal file
View File

@@ -0,0 +1,14 @@
package main
func main() {
a := 2
switch {
case a == 1:
println(1)
case a == 2:
}
println("bye")
}
// Output:
// bye

15
_test/switch37.go Normal file
View File

@@ -0,0 +1,15 @@
package main
func main() {
a := 2
switch {
case a == 1:
println(1)
case a == 3:
default:
}
println("bye")
}
// Output:
// bye

15
_test/time12.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import (
"fmt"
"time"
)
var twentyFourHours = time.Duration(24 * time.Hour)
func main() {
fmt.Println(twentyFourHours.Hours())
}
// Output:
// 24

16
_test/type21.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"reflect"
"time"
)
func main() {
t := time.Date(2009, time.November, 10, 23, 4, 5, 0, time.UTC)
v := reflect.ValueOf(t.String)
f := v.Interface().(func() string)
println(f())
}
// Output:
// 2009-11-10 23:04:05 +0000 UTC

18
_test/type22.go Normal file
View File

@@ -0,0 +1,18 @@
package main
type T1 T
func foo() T1 {
return T1(T{"foo"})
}
type T struct {
Name string
}
func main() {
println(foo().Name)
}
// Output:
// foo

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

20
_test/variadic6.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import "fmt"
type A struct {
}
func (a A) f(vals ...bool) {
for _, v := range vals {
fmt.Println(v)
}
}
func main() {
bools := []bool{true}
a := A{}
a.f(bools...)
}
// Output:
// true

20
_test/variadic7.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import "fmt"
func main() {
var a, b string
pattern := "%s %s"
dest := []interface{}{&a, &b}
n, err := fmt.Sscanf("test1 test2", pattern, dest...)
if err != nil || n != len(dest) {
println("error")
return
}
println(a, b)
}
// Output:
// test1 test2

View File

@@ -30,6 +30,7 @@ import (
"go/types"
"io/ioutil"
"log"
"math/big"
"os"
"path"
"runtime"
@@ -150,7 +151,7 @@ func genContent(dest, pkgName, license string) ([]byte, error) {
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}
}
@@ -253,35 +254,35 @@ func genContent(dest, pkgName, license string) ([]byte, error) {
}
// fixConst checks untyped constant value, converting it if necessary to avoid overflow
func fixConst(name string, val constant.Value) string {
func fixConst(name string, val constant.Value, 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

View File

@@ -1,22 +1,66 @@
/*
Yaegi interprets Go programs.
Yaegi reads Go language programs from its standard input or from a file
and evaluates them.
Yaegi reads Go language programs from standard input, string
parameters or files and run them.
If invoked with no arguments, it processes the standard input
in a Read-Eval-Print-Loop. A prompt is displayed if standard input
is a terminal.
If invoked with no arguments, it processes the standard input in
a Read-Eval-Print-Loop. A prompt is displayed if standard input is
a terminal.
Given a file, it operates on that file. If the first line starts with
"#!/usr/bin/env yaegi", and the file has exec permission, then the file
can be invoked directly from the shell.
File Mode
In file mode, as in standard Go, files are read entirely, then parsed,
then evaluated. In REPL mode, each line is parsed and evaluated separately,
at global level in an implicit main package.
In file mode, as in a standard Go compiler, source files are read entirely
before being parsed, then evaluated. It allows to handle forward
declarations and to have package code split in multiple source files.
Go specifications fully apply in this mode.
All files are interpreted in file mode except the initial file if it
starts with "#!" characters (the shebang pattern to allow executable
scripts), for example "#!/usr/bin/env yaegi". In that case, the initial
file is interpreted in REPL mode.
REPL mode
In REPL mode, the interpreter parses the code incrementally. As soon
as a statement is complete, it evaluates it. This makes the interpreter
suitable for interactive command line and scripts.
Go specifications apply with the following differences:
All local and global declarations (const, var, type, func) are allowed,
including in short form, except that all identifiers must be defined
before use (as declarations inside a standard Go function).
The statements are evaluated in the global space, within an implicit
"main" package.
It is not necessary to have a package statement, or a main function in
REPL mode. Import statements for preloaded binary packages can also
be avoided (i.e. all the standard library except the few packages
where default names collide, as "math/rand" and "crypto/rand", for which
an explicit import is still necessary).
Note that the source packages are always interpreted in file mode,
even if imported from REPL.
The following extract is a valid executable script:
#!/usr/bin/env yaegi
helloHandler := func(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "Hello, world!\n")
}
http.HandleFunc("/hello", helloHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
Example of a one liner:
$ yaegi -e 'println(reflect.TypeOf(fmt.Print))'
Options:
-e string
evaluate the string and return.
-i
start an interactive REPL after file execution.
-tags tag,list
@@ -28,6 +72,11 @@ Debugging support (may be removed at any time):
Generate and display graphviz dot of AST with dotty(1)
YAEGI_CFG_DOT=1
Generate and display graphviz dot of CFG with dotty(1)
YAEGI_DOT_CMD='dot -Tsvg -ofoo.svg'
Defines how to process the dot code generated whenever YAEGI_AST_DOT and/or
YAEGI_CFG_DOT is enabled. If any of YAEGI_AST_DOT or YAEGI_CFG_DOT is set,
but YAEGI_DOT_CMD is not defined, the default is to write to a .dot file
next to the Go source file.
*/
package main
@@ -84,17 +133,19 @@ func main() {
log.Fatal("Could not read file: ", args[0])
}
s := string(b)
if s[:2] == "#!" {
// Allow executable go scripts, but fix them prior to parse
if s := string(b); strings.HasPrefix(s, "#!") {
// Allow executable go scripts, Have the same behavior as in interactive mode.
s = strings.Replace(s, "#!", "//", 1)
}
i.Name = args[0]
if _, err := i.Eval(s); err != nil {
fmt.Println(err)
if p, ok := err.(interp.Panic); ok {
fmt.Println(string(p.Stack))
i.REPL(strings.NewReader(s), os.Stdout)
} else {
// Files not starting with "#!" are supposed to be pure Go, directly Evaled.
i.Name = args[0]
_, err := i.Eval(s)
if err != nil {
fmt.Println(err)
if p, ok := err.(interp.Panic); ok {
fmt.Println(string(p.Stack))
}
}
}

View File

@@ -6,10 +6,34 @@ import (
"os"
"os/exec"
"path/filepath"
"strconv"
"testing"
"time"
)
const (
// CITimeoutMultiplier is the multiplier for all timeouts in the CI
CITimeoutMultiplier = 3
)
// Sleep pauses the current goroutine for at least the duration d.
func Sleep(d time.Duration) {
d = applyCIMultiplier(d)
time.Sleep(d)
}
func applyCIMultiplier(timeout time.Duration) time.Duration {
ci := os.Getenv("CI")
if ci == "" {
return timeout
}
b, err := strconv.ParseBool(ci)
if err != nil || !b {
return timeout
}
return time.Duration(float64(timeout) * CITimeoutMultiplier)
}
func TestYaegiCmdCancel(t *testing.T) {
tmp, err := ioutil.TempDir("", "yaegi-")
if err != nil {
@@ -56,7 +80,7 @@ func TestYaegiCmdCancel(t *testing.T) {
if err != nil {
t.Errorf("failed pipe test source to yaegi command: %v", err)
}
time.Sleep(100 * time.Millisecond)
Sleep(200 * time.Millisecond)
err = cmd.Process.Signal(os.Interrupt)
if err != nil {
t.Errorf("failed to send os.Interrupt to yaegi command: %v", err)
@@ -84,7 +108,7 @@ func TestYaegiCmdCancel(t *testing.T) {
continue
}
if outBuf.String() != "context canceled\n2\n" {
if outBuf.String() != "context canceled\n" {
t.Errorf("unexpected output: %q", &outBuf)
}
}

View File

@@ -5,6 +5,7 @@ import (
"go/format"
"io/ioutil"
"log"
"strings"
"text/template"
)
@@ -12,14 +13,18 @@ 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}}
func {{$name}}(n *node) {
dest := genValue(n)
next := getExec(n.tnext)
typ := n.typ.TypeOf()
typ := n.typ.concrete().TypeOf()
dest := genValueOutput(n, typ)
c0, c1 := n.child[0], n.child[1]
switch typ.Kind() {
@@ -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}}
@@ -379,7 +409,7 @@ func {{$name}}Const(n *node) {
{{range $name, $op := .Comparison}}
func {{$name}}(n *node) {
tnext := getExec(n.tnext)
dest := genValue(n)
dest := genValueOutput(n, reflect.TypeOf(true))
c0, c1 := n.child[0], n.child[1]
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
@@ -822,6 +852,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,10 +3,10 @@ package interp
import (
"fmt"
"go/ast"
"go/constant"
"go/parser"
"go/scanner"
"go/token"
"math"
"reflect"
"strconv"
"sync/atomic"
@@ -32,6 +32,7 @@ const (
caseClause
chanType
commClause
commClauseDefault
compositeLitExpr
constDecl
continueStmt
@@ -94,80 +95,81 @@ const (
)
var kinds = [...]string{
undefNode: "undefNode",
addressExpr: "addressExpr",
arrayType: "arrayType",
assignStmt: "assignStmt",
assignXStmt: "assignXStmt",
basicLit: "basicLit",
binaryExpr: "binaryExpr",
blockStmt: "blockStmt",
branchStmt: "branchStmt",
breakStmt: "breakStmt",
callExpr: "callExpr",
caseBody: "caseBody",
caseClause: "caseClause",
chanType: "chanType",
commClause: "commClause",
compositeLitExpr: "compositeLitExpr",
constDecl: "constDecl",
continueStmt: "continueStmt",
declStmt: "declStmt",
deferStmt: "deferStmt",
defineStmt: "defineStmt",
defineXStmt: "defineXStmt",
ellipsisExpr: "ellipsisExpr",
exprStmt: "exprStmt",
fallthroughtStmt: "fallthroughStmt",
fieldExpr: "fieldExpr",
fieldList: "fieldList",
fileStmt: "fileStmt",
forStmt0: "forStmt0",
forStmt1: "forStmt1",
forStmt2: "forStmt2",
forStmt3: "forStmt3",
forStmt3a: "forStmt3a",
forStmt4: "forStmt4",
forRangeStmt: "forRangeStmt",
funcDecl: "funcDecl",
funcType: "funcType",
funcLit: "funcLit",
goStmt: "goStmt",
gotoStmt: "gotoStmt",
identExpr: "identExpr",
ifStmt0: "ifStmt0",
ifStmt1: "ifStmt1",
ifStmt2: "ifStmt2",
ifStmt3: "ifStmt3",
importDecl: "importDecl",
importSpec: "importSpec",
incDecStmt: "incDecStmt",
indexExpr: "indexExpr",
interfaceType: "interfaceType",
keyValueExpr: "keyValueExpr",
labeledStmt: "labeledStmt",
landExpr: "landExpr",
lorExpr: "lorExpr",
mapType: "mapType",
parenExpr: "parenExpr",
rangeStmt: "rangeStmt",
returnStmt: "returnStmt",
selectStmt: "selectStmt",
selectorExpr: "selectorExpr",
selectorImport: "selectorImport",
sendStmt: "sendStmt",
sliceExpr: "sliceExpr",
starExpr: "starExpr",
structType: "structType",
switchStmt: "switchStmt",
switchIfStmt: "switchIfStmt",
typeAssertExpr: "typeAssertExpr",
typeDecl: "typeDecl",
typeSpec: "typeSpec",
typeSwitch: "typeSwitch",
unaryExpr: "unaryExpr",
valueSpec: "valueSpec",
varDecl: "varDecl",
undefNode: "undefNode",
addressExpr: "addressExpr",
arrayType: "arrayType",
assignStmt: "assignStmt",
assignXStmt: "assignXStmt",
basicLit: "basicLit",
binaryExpr: "binaryExpr",
blockStmt: "blockStmt",
branchStmt: "branchStmt",
breakStmt: "breakStmt",
callExpr: "callExpr",
caseBody: "caseBody",
caseClause: "caseClause",
chanType: "chanType",
commClause: "commClause",
commClauseDefault: "commClauseDefault",
compositeLitExpr: "compositeLitExpr",
constDecl: "constDecl",
continueStmt: "continueStmt",
declStmt: "declStmt",
deferStmt: "deferStmt",
defineStmt: "defineStmt",
defineXStmt: "defineXStmt",
ellipsisExpr: "ellipsisExpr",
exprStmt: "exprStmt",
fallthroughtStmt: "fallthroughStmt",
fieldExpr: "fieldExpr",
fieldList: "fieldList",
fileStmt: "fileStmt",
forStmt0: "forStmt0",
forStmt1: "forStmt1",
forStmt2: "forStmt2",
forStmt3: "forStmt3",
forStmt3a: "forStmt3a",
forStmt4: "forStmt4",
forRangeStmt: "forRangeStmt",
funcDecl: "funcDecl",
funcType: "funcType",
funcLit: "funcLit",
goStmt: "goStmt",
gotoStmt: "gotoStmt",
identExpr: "identExpr",
ifStmt0: "ifStmt0",
ifStmt1: "ifStmt1",
ifStmt2: "ifStmt2",
ifStmt3: "ifStmt3",
importDecl: "importDecl",
importSpec: "importSpec",
incDecStmt: "incDecStmt",
indexExpr: "indexExpr",
interfaceType: "interfaceType",
keyValueExpr: "keyValueExpr",
labeledStmt: "labeledStmt",
landExpr: "landExpr",
lorExpr: "lorExpr",
mapType: "mapType",
parenExpr: "parenExpr",
rangeStmt: "rangeStmt",
returnStmt: "returnStmt",
selectStmt: "selectStmt",
selectorExpr: "selectorExpr",
selectorImport: "selectorImport",
sendStmt: "sendStmt",
sliceExpr: "sliceExpr",
starExpr: "starExpr",
structType: "structType",
switchStmt: "switchStmt",
switchIfStmt: "switchIfStmt",
typeAssertExpr: "typeAssertExpr",
typeDecl: "typeDecl",
typeSpec: "typeSpec",
typeSwitch: "typeSwitch",
unaryExpr: "unaryExpr",
valueSpec: "valueSpec",
varDecl: "varDecl",
}
func (k nkind) String() string {
@@ -196,15 +198,20 @@ const (
aAndNot
aAndNotAssign
aBitNot
aBranch
aCall
aCallSlice
aCase
aCompositeLit
aConvert
aDec
aEqual
aGreater
aGreaterEqual
aGetFunc
aGetIndex
aGetMethod
aGetSym
aInc
aLand
aLor
@@ -253,14 +260,19 @@ var actions = [...]string{
aAndNot: "&^",
aAndNotAssign: "&^=",
aBitNot: "^",
aBranch: "branch",
aCall: "call",
aCallSlice: "callSlice",
aCase: "case",
aCompositeLit: "compositeLit",
aConvert: "convert",
aDec: "--",
aEqual: "==",
aGreater: ">",
aGetFunc: "getFunc",
aGetIndex: "getIndex",
aGetMethod: "getMethod",
aGetSym: ".",
aInc: "++",
aLand: "&&",
aLor: "||",
@@ -463,18 +475,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)
if math.Trunc(v) == v {
n.rval = reflect.ValueOf(int(v))
} else {
n.rval = reflect.ValueOf(v)
}
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)
@@ -546,7 +554,12 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
st.push(addChild(&root, anc, pos, kind, aNop), nod)
case *ast.CallExpr:
st.push(addChild(&root, anc, pos, callExpr, aCall), nod)
action := aCall
if a.Ellipsis != token.NoPos {
action = aCallSlice
}
st.push(addChild(&root, anc, pos, callExpr, action), nod)
case *ast.CaseClause:
st.push(addChild(&root, anc, pos, caseClause, aCase), nod)
@@ -555,7 +568,11 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
st.push(addChild(&root, anc, pos, chanType, aNop), nod)
case *ast.CommClause:
st.push(addChild(&root, anc, pos, commClause, aNop), nod)
kind := commClause
if a.Comm == nil {
kind = commClauseDefault
}
st.push(addChild(&root, anc, pos, kind, aNop), nod)
case *ast.CommentGroup:
return false

View File

@@ -2,6 +2,7 @@ package interp
import (
"fmt"
"go/constant"
"log"
"math"
"reflect"
@@ -50,7 +51,6 @@ var identifier = regexp.MustCompile(`([\pL_][\pL_\d]*)$`)
func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
sc := interp.initScopePkg(pkgID)
var initNodes []*node
var iotaValue int
var err error
root.Walk(func(n *node) bool {
@@ -90,6 +90,8 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
switch typ.Kind() {
case reflect.Map:
n.anc.gen = rangeMap
ityp := &itype{cat: valueT, rtype: reflect.TypeOf((*reflect.MapIter)(nil))}
sc.add(ityp)
ktyp = &itype{cat: valueT, rtype: typ.Key()}
vtyp = &itype{cat: valueT, rtype: typ.Elem()}
case reflect.String:
@@ -202,9 +204,12 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
nod.typ = typ
}
case commClauseDefault:
sc = sc.pushBloc()
case commClause:
sc = sc.pushBloc()
if n.child[0].action == aAssign {
if len(n.child) > 0 && n.child[0].action == aAssign {
ch := n.child[0].child[1].child[0]
var typ *itype
if typ, err = nodeType(interp, sc, ch); err != nil {
@@ -247,6 +252,10 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
case binaryExpr, unaryExpr:
// Do not attempt to propagate composite type to operator expressions,
// it breaks constant folding.
case callExpr:
if c.typ, err = nodeType(interp, sc, c); err != nil {
return false
}
default:
c.typ = n.typ
}
@@ -350,27 +359,47 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
case typeSpec:
// processing already done in GTA pass for global types, only parses inlined types
if sc.def != nil {
typeName := n.child[0].ident
var typ *itype
if typ, err = nodeType(interp, sc, n.child[1]); err != nil {
return false
}
if typ.incomplete {
err = n.cfgErrorf("invalid type declaration")
return false
}
if n.child[1].kind == identExpr {
n.typ = &itype{cat: aliasT, val: typ, name: typeName}
} else {
n.typ = typ
n.typ.name = typeName
}
sc.sym[typeName] = &symbol{kind: typeSym, typ: n.typ}
if sc.def == nil {
return false
}
typeName := n.child[0].ident
var typ *itype
if typ, err = nodeType(interp, sc, n.child[1]); err != nil {
return false
}
if typ.incomplete {
err = n.cfgErrorf("invalid type declaration")
return false
}
if _, exists := sc.sym[typeName]; exists {
// TODO(mpl): find the exact location of the previous declaration
err = n.cfgErrorf("%s redeclared in this block", typeName)
return false
}
if n.child[1].kind == identExpr {
n.typ = &itype{cat: aliasT, val: typ, name: typeName}
} else {
n.typ = typ
n.typ.name = typeName
}
sc.sym[typeName] = &symbol{kind: typeSym, typ: n.typ}
return false
case arrayType, basicLit, chanType, funcType, mapType, structType:
case constDecl:
// Early parse of constDecl subtrees, to compute all constant
// values which may be used in further declarations.
if !sc.global {
for _, c := range n.child {
if _, err = interp.cfg(c, pkgID); err != nil {
// No error processing here, to allow recovery in subtree nodes.
err = nil
}
}
}
case arrayType, basicLit, chanType, funcType, interfaceType, mapType, structType:
n.typ, err = nodeType(interp, sc, n)
return false
}
@@ -482,7 +511,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
// Propagate type
// TODO: Check that existing destination type matches source type
switch {
case n.action == aAssign && src.action == aCall && dest.typ.cat != interfaceT:
case n.action == aAssign && isCall(src) && dest.typ.cat != interfaceT && !isRecursiveField(dest):
// Call action may perform the assignment directly.
n.gen = nop
src.level = level
@@ -495,7 +524,14 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.gen = nop
src.findex = dest.findex // Set recv address to LHS
dest.typ = src.typ
case n.action == aAssign && src.action == aCompositeLit:
case n.action == aAssign && src.action == aCompositeLit && !isMapEntry(dest):
if dest.typ.cat == valueT && dest.typ.rtype.Kind() == reflect.Interface {
// Skip optimisation for assigned binary interface or map entry
// which require and additional operation to set the value
break
}
// Skip the assign operation entirely, the source frame index is set
// to destination index, avoiding extra memory alloc and duplication.
n.gen = nop
src.findex = dest.findex
src.level = level
@@ -508,8 +544,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
}
@@ -524,8 +563,18 @@ 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
iotaValue++
if childPos(n) == len(n.anc.child)-1 {
sc.iota = 0
} else {
sc.iota++
}
}
}
@@ -542,22 +591,28 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
case assignXStmt:
wireChild(n)
l := len(n.child) - 1
switch n.child[l].kind {
switch lc := n.child[l]; lc.kind {
case callExpr:
if n.child[l-1].isType(sc) {
l--
}
if r := lc.child[0].typ.numOut(); r != l {
err = n.cfgErrorf("assignment mismatch: %d variables but %s returns %d values", l, lc.child[0].name(), r)
}
n.gen = nop
case indexExpr:
n.child[l].gen = getIndexMap2
lc.gen = getIndexMap2
n.gen = nop
case typeAssertExpr:
if n.child[0].ident == "_" {
n.child[l].gen = typeAssertStatus
lc.gen = typeAssertStatus
} else {
n.child[l].gen = typeAssert2
lc.gen = typeAssert2
}
n.gen = nop
case unaryExpr:
if n.child[l].action == aRecv {
n.child[l].gen = recv2
if lc.action == aRecv {
lc.gen = recv2
n.gen = nop
}
}
@@ -565,7 +620,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
case defineXStmt:
wireChild(n)
if sc.def == nil {
// in global scope, type definition already handled by GTA
// In global scope, type definition already handled by GTA.
break
}
err = compDefineX(sc, n)
@@ -575,8 +630,8 @@ 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
// 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())
break
@@ -590,10 +645,15 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
if !(isNumber(t0) && isNumber(t1)) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
case aRem, aAnd, aOr, aXor, aAndNot:
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)
@@ -620,33 +680,35 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
if err != nil {
break
}
if c0.rval.IsValid() && c1.rval.IsValid() && constOp[n.action] != nil {
if n.typ == nil {
if n.typ, err = nodeType(interp, sc, n); err != nil {
return
}
if n.typ == nil {
if n.typ, err = nodeType(interp, sc, n); err != nil {
break
}
n.typ.TypeOf() // init reflect type
constOp[n.action](n)
}
if c0.rval.IsValid() && c1.rval.IsValid() && !isInterface(n.typ) && constOp[n.action] != nil {
n.typ.TypeOf() // Force compute of reflection type.
constOp[n.action](n) // Compute a constant result now rather than during exec.
}
switch {
case n.rval.IsValid():
// This operation involved constants, and the result is already computed
// by constOp and available in n.rval. Nothing else to do at execution.
n.gen = nop
n.findex = -1
case n.anc.kind == assignStmt && n.anc.action == aAssign:
// To avoid a copy in frame, if the result is to be assigned, store it directly
// at the frame location of destination.
dest := n.anc.child[childPos(n)-n.anc.nright]
n.typ = dest.typ
n.findex = dest.findex
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.
pos := childPos(n)
n.typ = sc.def.typ.ret[pos]
n.findex = pos
default:
if n.typ == nil {
if n.typ, err = nodeType(interp, sc, n); err != nil {
return
}
}
// Allocate a new location in frame, and store the result here.
n.findex = sc.add(n.typ)
}
@@ -654,7 +716,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
wireChild(n)
t := n.child[0].typ
switch t.cat {
case ptrT:
case aliasT, ptrT:
n.typ = t.val
if t.val.cat == valueT {
n.typ = &itype{cat: valueT, rtype: t.val.rtype.Elem()}
@@ -673,7 +735,6 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.typ = t.val
}
n.findex = sc.add(n.typ)
n.recv = &receiver{node: n}
typ := t.TypeOf()
switch k := typ.Kind(); k {
case reflect.Map:
@@ -702,11 +763,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
sc = sc.pop()
case constDecl:
iotaValue = 0
wireChild(n)
case varDecl:
case constDecl, varDecl:
wireChild(n)
case declStmt, exprStmt, sendStmt:
@@ -749,14 +806,18 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
if n.typ, err = nodeType(interp, sc, n); err != nil {
return
}
if n.typ.cat == builtinT {
switch {
case n.typ.cat == builtinT:
n.findex = -1
n.val = nil
} else {
case n.anc.kind == returnStmt:
// Store result directly to frame output location, to avoid a frame copy.
n.findex = 0
default:
n.findex = sc.add(n.typ)
}
if op, ok := constBltn[n.child[0].ident]; ok && n.anc.action != aAssign {
op(n) // pre-compute non-assigned constant builtin calls
op(n) // pre-compute non-assigned constant :
}
case n.child[0].isType(sc):
@@ -764,6 +825,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
if isInt(n.child[0].typ.TypeOf()) && n.child[1].kind == basicLit && isFloat(n.child[1].typ.TypeOf()) {
err = n.cfgErrorf("truncated to integer")
}
n.action = aConvert
if isInterface(n.child[0].typ) && !n.child[1].isNil() {
// Convert to interface: just check that all required methods are defined by concrete type.
c0, c1 := n.child[0], n.child[1]
@@ -783,7 +845,23 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
case isBinCall(n):
n.gen = callBin
if typ := n.child[0].typ.rtype; typ.NumOut() > 0 {
typ := n.child[0].typ.rtype
numIn := len(n.child) - 1
tni := typ.NumIn()
if numIn == 1 && isCall(n.child[1]) {
numIn = n.child[1].typ.numOut()
}
if n.child[0].action == aGetMethod {
tni-- // The first argument is the method receiver.
}
if typ.IsVariadic() {
tni-- // The last argument could be empty.
}
if numIn < tni {
err = n.cfgErrorf("not enough arguments in call to %v", n.child[0].name())
break
}
if typ.NumOut() > 0 {
if funcType := n.child[0].typ.val; funcType != nil {
// Use the original unwrapped function type, to allow future field and
// methods resolutions, otherwise impossible on the opaque bin type.
@@ -794,22 +872,30 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
} else {
n.typ = &itype{cat: valueT, rtype: typ.Out(0)}
n.findex = sc.add(n.typ)
for i := 1; i < typ.NumOut(); i++ {
sc.add(&itype{cat: valueT, rtype: typ.Out(i)})
if n.anc.kind == returnStmt {
n.findex = childPos(n)
} else {
n.findex = sc.add(n.typ)
for i := 1; i < typ.NumOut(); i++ {
sc.add(&itype{cat: valueT, rtype: typ.Out(i)})
}
}
}
}
default:
if n.child[0].action == aGetFunc {
// allocate frame entry for anonymous function
// Allocate a frame entry to store the anonymous function definition.
sc.add(n.child[0].typ)
}
if typ := n.child[0].typ; len(typ.ret) > 0 {
n.typ = typ.ret[0]
n.findex = sc.add(n.typ)
for _, t := range typ.ret[1:] {
sc.add(t)
if n.anc.kind == returnStmt {
n.findex = childPos(n)
} else {
n.findex = sc.add(n.typ)
for _, t := range typ.ret[1:] {
sc.add(t)
}
}
} else {
n.findex = -1
@@ -822,7 +908,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
case typeSwichAssign(n) && len(n.child) > 1:
n.start = n.child[1].start
case len(n.child) == 0:
// empty case body: jump to switch node (exit node)
// Empty case body: jump to switch node (exit node).
n.start = n.anc.anc.anc
default:
n.start = n.child[0].start
@@ -831,21 +917,29 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
case caseClause:
sc = sc.pop()
case commClauseDefault:
wireChild(n)
sc = sc.pop()
if len(n.child) == 0 {
return
}
n.start = n.child[0].start
n.lastChild().tnext = n.anc.anc // exit node is selectStmt
case commClause:
wireChild(n)
sc = sc.pop()
if len(n.child) == 0 {
return
}
if len(n.child) > 1 {
n.start = n.child[1].start // Skip chan operation, performed by select
} else {
n.start = n.child[0].start // default clause
}
n.lastChild().tnext = n.anc.anc // exit node is selectStmt
sc = sc.pop()
case compositeLitExpr:
wireChild(n)
if n.anc.action != aAssign {
n.findex = sc.add(n.typ)
}
n.findex = sc.add(n.typ)
// TODO: Check that composite literal expr matches corresponding type
n.gen = compositeGenerator(n, sc)
@@ -881,7 +975,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
cond.tnext = body.start
body.tnext = cond.start
}
cond.fnext = n
setFNext(cond, n)
sc = sc.pop()
case forStmt2: // for init; cond; {}
@@ -903,7 +997,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
body.tnext = cond.start
}
cond.tnext = body.start
cond.fnext = n
setFNext(cond, n)
sc = sc.pop()
case forStmt3: // for ; cond; post {}
@@ -922,7 +1016,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
post.tnext = cond.start
}
cond.tnext = body.start
cond.fnext = n
setFNext(cond, n)
body.tnext = post.start
sc = sc.pop()
@@ -953,13 +1047,13 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
post.tnext = cond.start
}
cond.tnext = body.start
cond.fnext = n
setFNext(cond, n)
body.tnext = post.start
sc = sc.pop()
case forRangeStmt:
n.start = n.child[0].start
n.child[0].fnext = n
setFNext(n.child[0], n)
sc = sc.pop()
case funcDecl:
@@ -998,7 +1092,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.rval = sym.rval
n.kind = basicLit
case n.ident == "iota":
n.rval = reflect.ValueOf(iotaValue)
n.rval = reflect.ValueOf(constant.Make(int64(sc.iota)))
n.kind = basicLit
case n.ident == "nil":
n.kind = basicLit
@@ -1010,9 +1104,6 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
err = n.cfgErrorf("use of builtin %s not in function call", n.ident)
}
}
if sym.kind == varSym && sym.typ != nil && sym.typ.TypeOf().Kind() == reflect.Bool {
fixBranch(n)
}
}
if n.sym != nil {
n.recv = n.sym.recv
@@ -1035,7 +1126,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.start = cond.start
cond.tnext = tbody.start
}
cond.fnext = n
setFNext(cond, n)
tbody.tnext = n
sc = sc.pop()
@@ -1054,7 +1145,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
} else {
n.start = cond.start
cond.tnext = tbody.start
cond.fnext = fbody.start
setFNext(cond, fbody.start)
}
tbody.tnext = n
fbody.tnext = n
@@ -1078,7 +1169,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
cond.tnext = tbody.start
}
tbody.tnext = n
cond.fnext = n
setFNext(cond, n)
sc = sc.pop()
case ifStmt3: // if init; cond {} else {}
@@ -1097,7 +1188,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
} else {
init.tnext = cond.start
cond.tnext = tbody.start
cond.fnext = fbody.start
setFNext(cond, fbody.start)
}
tbody.tnext = n
fbody.tnext = n
@@ -1109,7 +1200,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
case landExpr:
n.start = n.child[0].start
n.child[0].tnext = n.child[1].start
n.child[0].fnext = n
setFNext(n.child[0], n)
n.child[1].tnext = n
n.typ = n.child[0].typ
n.findex = sc.add(n.typ)
@@ -1120,7 +1211,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
case lorExpr:
n.start = n.child[0].start
n.child[0].tnext = n
n.child[0].fnext = n.child[1].start
setFNext(n.child[0], n.child[1].start)
n.child[1].tnext = n
n.typ = n.child[0].typ
n.findex = sc.add(n.typ)
@@ -1158,6 +1249,16 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
case returnStmt:
if mustReturnValue(sc.def.child[2]) {
nret := len(n.child)
if nret == 1 && isCall(n.child[0]) {
nret = n.child[0].child[0].typ.numOut()
}
if nret < sc.def.typ.numOut() {
err = n.cfgErrorf("not enough arguments to return")
break
}
}
wireChild(n)
n.tnext = nil
n.val = sc.def
@@ -1194,6 +1295,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
case ok:
n.val = method.Index
n.gen = getIndexBinMethod
n.action = aGetMethod
n.recv = &receiver{node: n.child[0]}
n.typ = &itype{cat: valueT, rtype: method.Type, isBinMethod: true}
case n.typ.rtype.Kind() == reflect.Ptr:
@@ -1217,6 +1319,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.gen = getIndexBinPtrMethod
n.typ = &itype{cat: valueT, rtype: m2.Type}
n.recv = &receiver{node: n.child[0]}
n.action = aGetMethod
} else {
err = n.cfgErrorf("undefined field or method: %s", n.child[1].ident)
}
@@ -1231,11 +1334,13 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.typ = &itype{cat: valueT, rtype: method.Type}
n.recv = &receiver{node: n.child[0]}
n.gen = getIndexBinMethod
n.action = aGetMethod
} else if method, ok := reflect.PtrTo(n.typ.val.rtype).MethodByName(n.child[1].ident); ok {
n.val = method.Index
n.gen = getIndexBinMethod
n.typ = &itype{cat: valueT, rtype: method.Type}
n.recv = &receiver{node: n.child[0]}
n.action = aGetMethod
} else if field, ok := n.typ.val.rtype.FieldByName(n.child[1].ident); ok {
n.typ = &itype{cat: valueT, rtype: field.Type}
n.val = field.Index
@@ -1254,6 +1359,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.typ = &itype{cat: valueT, rtype: s.Type(), untyped: isValueUntyped(s)}
n.rval = s
}
n.action = aGetSym
n.gen = nop
} else {
err = n.cfgErrorf("package %s \"%s\" has no symbol %s", n.child[0].ident, pkg, name)
@@ -1265,6 +1371,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.findex = sym.index
n.val = sym.node
n.gen = nop
n.action = aGetSym
n.typ = sym.typ
n.sym = sym
n.rval = sym.rval
@@ -1272,6 +1379,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
err = n.cfgErrorf("undefined selector: %s.%s", pkg, name)
}
} else if m, lind := n.typ.lookupMethod(n.child[1].ident); m != nil {
n.action = aGetMethod
if n.child[0].isType(sc) {
// Handle method as a function with receiver in 1st argument
n.val = m
@@ -1288,6 +1396,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.recv = &receiver{node: n.child[0], index: lind}
}
} else if m, lind, isPtr, ok := n.typ.lookupBinMethod(n.child[1].ident); ok {
n.action = aGetMethod
if isPtr && n.typ.fieldSeq(lind).cat != ptrT {
n.gen = getIndexSeqPtrMethod
} else {
@@ -1341,28 +1450,37 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
// Chain channel init actions in commClauses prior to invoke select.
var cur *node
for _, c := range n.child[0].child {
var an *node // channel init action node
c0 := c.child[0]
switch {
case c0.kind == exprStmt && len(c0.child) == 1 && c0.child[0].action == aRecv:
an = c0.child[0].child[0]
case c0.action == aAssign:
an = c0.lastChild().child[0]
case c0.kind == sendStmt:
an = c0.child[0]
}
if an != nil {
if cur == nil {
// First channel init action, the entry point for the select block.
n.start = an.start
} else {
// Chain channel init action to the previous one.
cur.tnext = an.start
var an, pn *node // channel init action nodes
if len(c.child) > 0 {
switch c0 := c.child[0]; {
case c0.kind == exprStmt && len(c0.child) == 1 && c0.child[0].action == aRecv:
an = c0.child[0].child[0]
pn = an
case c0.action == aAssign:
an = c0.lastChild().child[0]
pn = an
case c0.kind == sendStmt:
an = c0.child[0]
pn = c0.child[1]
}
}
cur = an
if an == nil {
continue
}
if cur == nil {
// First channel init action, the entry point for the select block.
n.start = an.start
} else {
// Chain channel init action to the previous one.
cur.tnext = an.start
}
if pn != nil {
// Chain channect init action to send data init action.
// (already done by wireChild, but let's be explicit).
an.tnext = pn
cur = pn
}
}
// Invoke select action
if cur == nil {
// There is no channel init action, call select directly.
n.start = n.child[0]
@@ -1423,7 +1541,8 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
// Chain case clauses.
for i, c := range clauses[:l-1] {
c.fnext = clauses[i+1] // Chain to next clause.
// Chain to next clause.
setFNext(c, clauses[i+1])
if len(c.child) == 0 {
c.tnext = n // Clause body is empty, exit.
} else {
@@ -1444,6 +1563,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
}
c := clauses[l-1] // Last clause.
c.fnext = n
if len(c.child) == 0 {
c.tnext = n // Clause body is empty, exit.
} else {
@@ -1467,24 +1587,29 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
for i := l - 1; i >= 0; i-- {
c := clauses[i]
c.gen = nop
body := c.lastChild()
if len(c.child) > 1 {
cond := c.child[0]
cond.tnext = body.start
if i == l-1 {
cond.fnext = n
if len(c.child) == 0 {
c.tnext = n
c.fnext = n
} else {
body := c.lastChild()
if len(c.child) > 1 {
cond := c.child[0]
cond.tnext = body.start
if i == l-1 {
setFNext(cond, n)
} else {
setFNext(cond, clauses[i+1].start)
}
c.start = cond.start
} else {
cond.fnext = clauses[i+1].start
c.start = body.start
}
// If last case body statement is a fallthrough, then jump to next case body
if i < l-1 && len(body.child) > 0 && body.lastChild().kind == fallthroughtStmt {
body.tnext = clauses[i+1].lastChild().start
} else {
body.tnext = n
}
c.start = cond.start
} else {
c.start = body.start
}
// If last case body statement is a fallthrough, then jump to next case body
if i < l-1 && len(body.child) > 0 && body.lastChild().kind == fallthroughtStmt {
body.tnext = clauses[i+1].lastChild().start
} else {
body.tnext = n
}
}
sbn.start = clauses[0].start
@@ -1500,7 +1625,12 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
}
if n.anc.action != aAssignX {
n.typ = n.child[1].typ
if n.child[0].typ.cat == valueT {
// Avoid special wrapping of interfaces and func types.
n.typ = &itype{cat: valueT, rtype: n.child[1].typ.TypeOf()}
} else {
n.typ = n.child[1].typ
}
n.findex = sc.add(n.typ)
}
} else {
@@ -1541,13 +1671,13 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
return
}
}
// TODO: Optimisation: avoid allocation if boolean branch op (i.e. '!' in an 'if' expr)
if n.child[0].rval.IsValid() && constOp[n.action] != nil {
if n.typ == nil {
if n.typ, err = nodeType(interp, sc, n); err != nil {
return
}
if n.typ == nil {
if n.typ, err = nodeType(interp, sc, n); err != nil {
return
}
}
// TODO: Optimisation: avoid allocation if boolean branch op (i.e. '!' in an 'if' expr)
if n.child[0].rval.IsValid() && !isInterface(n.typ) && constOp[n.action] != nil {
n.typ.TypeOf() // init reflect type
constOp[n.action](n)
}
@@ -1564,11 +1694,6 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.typ = sc.def.typ.ret[pos]
n.findex = pos
default:
if n.typ == nil {
if n.typ, err = nodeType(interp, sc, n); err != nil {
return
}
}
n.findex = sc.add(n.typ)
}
@@ -1586,6 +1711,19 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
// Global object allocation is already performed in GTA.
index = sc.sym[c.ident].index
} else {
if sym, exists := sc.sym[c.ident]; exists {
if sym.typ.node != nil &&
sym.typ.node.anc != nil {
// for non-predeclared identifiers (struct, map, etc)
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
}
// for predeclared identifiers (int, string, etc)
// TODO(mpl): find the exact location of the previous declaration in all cases.
err = n.cfgErrorf("%s redeclared in this block", c.ident)
return
}
index = sc.add(n.typ)
sc.sym[c.ident] = &symbol{index: index, kind: varSym, typ: n.typ}
}
@@ -1619,6 +1757,12 @@ func compDefineX(sc *scope, n *node) error {
} else {
types = funtype.ret
}
if n.child[l-1].isType(sc) {
l--
}
if len(types) != l {
return n.cfgErrorf("assignment mismatch: %d variables but %s returns %d values", l, src.child[0].name(), len(types))
}
n.gen = nop
case indexExpr:
@@ -1657,13 +1801,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 {
@@ -1703,15 +1847,19 @@ func genRun(nod *node) error {
return err
}
// FixBranch sets the branch action to the identExpr node if it is a bool
// used in a conditional expression.
func fixBranch(n *node) {
switch n.anc.kind {
case ifStmt0, ifStmt1, ifStmt2, ifStmt3, forStmt1, forStmt2, forStmt3, forStmt4:
n.gen = branch
case parenExpr:
fixBranch(n.anc)
// setFnext sets the cond fnext field to next, propagates it for parenthesis blocks
// and sets the action to branch.
func setFNext(cond, next *node) {
if cond.action == aNop {
cond.action = aBranch
cond.gen = branch
cond.fnext = next
}
if cond.kind == parenExpr {
setFNext(cond.lastChild(), next)
return
}
cond.fnext = next
}
// GetDefault return the index of default case clause in a switch statement, or -1.
@@ -1799,12 +1947,22 @@ func wireChild(n *node) {
}
}
func (n *node) name() (s string) {
switch {
case n.ident != "":
s = n.ident
case n.action == aGetSym:
s = n.child[0].ident + "." + n.child[1].ident
}
return s
}
// isInteger returns true if node type is integer, false otherwise
func (n *node) isInteger() bool {
if isInt(n.typ.TypeOf()) {
return true
}
if n.typ.untyped && n.rval.IsValid() {
if n.rval.IsValid() {
t := n.rval.Type()
if isInt(t) {
return true
@@ -1818,6 +1976,20 @@ 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
}
@@ -1827,7 +1999,7 @@ func (n *node) isNatural() bool {
if isUint(n.typ.TypeOf()) {
return true
}
if n.typ.untyped && n.rval.IsValid() {
if n.rval.IsValid() {
t := n.rval.Type()
if isUint(t) {
return true
@@ -1845,6 +2017,23 @@ 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
}
@@ -1890,6 +2079,10 @@ func isField(n *node) bool {
return n.kind == selectorExpr && len(n.child) > 0 && n.child[0].typ != nil && isStruct(n.child[0].typ)
}
func isRecursiveField(n *node) bool {
return isField(n) && (n.typ.recursive || n.typ.cat == ptrT && n.typ.val.recursive)
}
// isNewDefine returns true if node refers to a new definition
func isNewDefine(n *node, sc *scope) bool {
if n.ident == "_" {
@@ -1915,13 +2108,29 @@ func isMethod(n *node) bool {
}
func isMapEntry(n *node) bool {
return n.action == aGetIndex && n.child[0].typ.cat == mapT
return n.action == aGetIndex && isMap(n.child[0].typ)
}
func isCall(n *node) bool {
return n.action == aCall || n.action == aCallSlice
}
func isBinCall(n *node) bool {
return n.kind == callExpr && n.child[0].typ.cat == valueT && n.child[0].typ.rtype.Kind() == reflect.Func
}
func mustReturnValue(n *node) bool {
if len(n.child) < 2 {
return false
}
for _, f := range n.child[1].child {
if len(f.child) > 1 {
return false
}
}
return true
}
func isRegularCall(n *node) bool {
return n.kind == callExpr && n.child[0].typ.cat == funcT
}
@@ -2073,5 +2282,8 @@ func isValueUntyped(v reflect.Value) bool {
return false
}
t := v.Type()
if t.Implements(constVal) {
return true
}
return t.String() == t.Kind().String()
}

View File

@@ -3,8 +3,10 @@ package interp
import (
"fmt"
"io"
"io/ioutil"
"log"
"os/exec"
"path/filepath"
"strings"
)
@@ -58,10 +60,19 @@ func (n *node) cfgDot(out io.Writer) {
fmt.Fprintf(out, "}\n")
}
// dotX returns an output stream to a dot(1) co-process where to write data in .dot format
func dotX() io.WriteCloser {
cmd := exec.Command("dotty", "-")
//cmd := exec.Command("dot", "-T", "xlib")
type nopCloser struct {
io.Writer
}
func (nopCloser) Close() error { return nil }
// dotWriter returns an output stream to a dot(1) co-process where to write data in .dot format
func dotWriter(dotCmd string) io.WriteCloser {
if dotCmd == "" {
return nopCloser{ioutil.Discard}
}
fields := strings.Fields(dotCmd)
cmd := exec.Command(fields[0], fields[1:]...)
dotin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
@@ -71,3 +82,14 @@ func dotX() io.WriteCloser {
}
return dotin
}
func defaultDotCmd(filePath, prefix string) string {
dir, fileName := filepath.Split(filePath)
ext := filepath.Ext(fileName)
if ext == "" {
fileName += ".dot"
} else {
fileName = strings.Replace(fileName, ext, ".dot", 1)
}
return "dot -Tdot -o" + dir + prefix + fileName
}

View File

@@ -9,7 +9,6 @@ import "reflect"
func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error) {
sc := interp.initScopePkg(pkgID)
var err error
var iotaValue int
var revisit []*node
root.Walk(func(n *node) bool {
@@ -18,9 +17,8 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
}
switch n.kind {
case constDecl:
iotaValue = 0
// Early parse of constDecl subtree, to compute all constant
// values which may be necessary in further declarations.
// values which may be used in further declarations.
if _, err = interp.cfg(n, pkgID); err != nil {
// No error processing here, to allow recovery in subtree nodes.
err = nil
@@ -47,7 +45,7 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
for i := 0; i < n.nleft; i++ {
dest, src := n.child[i], n.child[sbase+i]
val := reflect.ValueOf(iotaValue)
val := reflect.ValueOf(sc.iota)
if n.anc.kind == constDecl {
if _, err2 := interp.cfg(n, pkgID); err2 != nil {
// Constant value can not be computed yet.
@@ -80,7 +78,11 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
}
if n.anc.kind == constDecl {
sc.sym[dest.ident].kind = constSym
iotaValue++
if childPos(n) == len(n.anc.child)-1 {
sc.iota = 0
} else {
sc.iota++
}
}
}
return false
@@ -135,6 +137,7 @@ 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
sc.sym[n.child[1].ident] = &symbol{kind: funcSym, typ: n.typ, node: n, index: -1}
@@ -151,9 +154,9 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
name = n.child[0].ident
} else {
ipath = n.child[0].rval.String()
name = identifier.FindString(ipath)
}
// Try to import a binary package first, or a source package
var pkgName string
if interp.binPkg[ipath] != nil {
switch name {
case "_": // no import of symbols
@@ -166,9 +169,13 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
sc.sym[n] = &symbol{kind: binSym, typ: &itype{cat: valueT, rtype: typ, scope: sc}, rval: v}
}
default: // import symbols in package namespace
if name == "" {
name = identifier.FindString(ipath)
}
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: binPkgT, path: ipath, scope: sc}}
}
} else if err = interp.importSrc(rpath, ipath); err == nil {
} else if pkgName, err = interp.importSrc(rpath, ipath); err == nil {
sc.types = interp.universe.types
switch name {
case "_": // no import of symbols
@@ -179,6 +186,10 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
}
}
default: // import symbols in package namespace
if name == "" {
name = pkgName
}
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT, path: ipath, scope: sc}}
}
} else {
@@ -192,7 +203,7 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
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}
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)
} else {
n.typ = typ

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