Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23dfef0ac8 | ||
|
|
4fd6a2dc56 | ||
|
|
92a63dbe09 | ||
|
|
e434892b9a | ||
|
|
712891dd77 | ||
|
|
137b16580c | ||
|
|
b057ada531 | ||
|
|
6d90c5a641 | ||
|
|
5381ee65d1 | ||
|
|
f1cde2be0f | ||
|
|
bb04af2d4d | ||
|
|
9a8a88dcb9 | ||
|
|
f3f54a5302 | ||
|
|
878fcc835c | ||
|
|
a1f2d3bf1d | ||
|
|
3cd37645eb | ||
|
|
e1ac83f7d8 | ||
|
|
4f93be7f19 | ||
|
|
7a0c09f5eb |
@@ -13,7 +13,12 @@ discussions.
|
|||||||
Once the proposal is approved, a Pull Request can be opened. If you want
|
Once the proposal is approved, a Pull Request can be opened. If you want
|
||||||
to provide early visibility to reviewers, create a [Draft Pull Request].
|
to provide early visibility to reviewers, create a [Draft Pull Request].
|
||||||
|
|
||||||
|
We will also require you to sign the [Containous Contributor License Agreement]
|
||||||
|
after you submit your first pull request to this project. The link to sign the
|
||||||
|
agreement will be presented to you in the web interface of the pull request.
|
||||||
|
|
||||||
[Issues]: https://github.com/containous/yaegi/issues
|
[Issues]: https://github.com/containous/yaegi/issues
|
||||||
[Pull Requests]: https://github.com/containous/yaegi/issues
|
[Pull Requests]: https://github.com/containous/yaegi/issues
|
||||||
[Feature Request]: https://github.com/containous/yaegi/issues/new?template=feature_request.md
|
[Feature Request]: https://github.com/containous/yaegi/issues/new?template=feature_request.md
|
||||||
[Draft Pull Request]: https://github.blog/2019-02-14-introducing-draft-pull-requests/
|
[Draft Pull Request]: https://github.blog/2019-02-14-introducing-draft-pull-requests/
|
||||||
|
[Containous Contributor License Agreement]: https://cla-assistant.io/containous/yaegi
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -129,6 +129,24 @@ $ yaegi cmd/yaegi/yaegi.go
|
|||||||
>
|
>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or for Go scripting in the shebang line:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ cat /tmp/test
|
||||||
|
#!/usr/bin/env yaegi
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("test")
|
||||||
|
}
|
||||||
|
$ ls -la /tmp/test
|
||||||
|
-rwxr-xr-x 1 dow184 dow184 93 Jan 6 13:38 /tmp/test
|
||||||
|
$ /tmp/test
|
||||||
|
test
|
||||||
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Documentation about Yaegi commands and libraries can be found at usual [godoc.org][docs].
|
Documentation about Yaegi commands and libraries can be found at usual [godoc.org][docs].
|
||||||
|
|||||||
7
_test/b1/foo/foo.go
Normal file
7
_test/b1/foo/foo.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
import bar "github.com/containous/yaegi/_test/b2/foo"
|
||||||
|
|
||||||
|
var Desc = "in b1/foo"
|
||||||
|
|
||||||
|
var Desc2 = Desc + bar.Desc
|
||||||
3
_test/b2/foo/foo.go
Normal file
3
_test/b2/foo/foo.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
var Desc = "in b2/foo"
|
||||||
13
_test/bin2.go
Normal file
13
_test/bin2.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println(math.Abs(-5))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// 5
|
||||||
18
_test/for8.go
Normal file
18
_test/for8.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
for {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if i == 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
println(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// 0
|
||||||
|
// 2
|
||||||
|
// 3
|
||||||
12
_test/fun11.go
Normal file
12
_test/fun11.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
var f F
|
||||||
|
|
||||||
|
type F func(int)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
println("ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// ok
|
||||||
10
_test/import8.go
Normal file
10
_test/import8.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/containous/yaegi/_test/b1/foo"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
println(foo.Desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// in b1/foo
|
||||||
12
_test/interface19.go
Normal file
12
_test/interface19.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
var I interface{}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Printf("%T %v\n", I, I)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// <nil> <nil>
|
||||||
16
_test/map19.go
Normal file
16
_test/map19.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type cmap struct {
|
||||||
|
servers map[int64]*server
|
||||||
|
}
|
||||||
|
|
||||||
|
type server struct {
|
||||||
|
cm *cmap
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
m := cmap{}
|
||||||
|
fmt.Println(m)
|
||||||
|
}
|
||||||
16
_test/method29.go
Normal file
16
_test/method29.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var lookupHost = net.DefaultResolver.LookupHost
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
res, err := lookupHost(context.Background(), "localhost")
|
||||||
|
println(len(res) > 0, err == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// true true
|
||||||
20
_test/method30.go
Normal file
20
_test/method30.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
type T struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *T) foo(a string) string {
|
||||||
|
return t.Name + a
|
||||||
|
}
|
||||||
|
|
||||||
|
var g = &T{"global"}
|
||||||
|
|
||||||
|
var f = g.foo
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
println(f("-x"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// global-x
|
||||||
15
_test/nil0.go
Normal file
15
_test/nil0.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func f() (host, port string, err error) {
|
||||||
|
return "", "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
h, p, err := f()
|
||||||
|
fmt.Println(h, p, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// <nil>
|
||||||
12
_test/nil1.go
Normal file
12
_test/nil1.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var a error = nil
|
||||||
|
|
||||||
|
if a == nil || a.Error() == "nil" {
|
||||||
|
println("a is nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// a is nil
|
||||||
9
_test/or0.go
Normal file
9
_test/or0.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c := false
|
||||||
|
println(c || !c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// true
|
||||||
9
_test/or1.go
Normal file
9
_test/or1.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c := false
|
||||||
|
println(!c || c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// true
|
||||||
26
_test/primes.go
Normal file
26
_test/primes.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
func Primes(n int) int {
|
||||||
|
var xs []int
|
||||||
|
for i := 2; len(xs) < n; i++ {
|
||||||
|
ok := true
|
||||||
|
for _, x := range xs {
|
||||||
|
if i%x == 0 {
|
||||||
|
ok = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
xs = append(xs, i)
|
||||||
|
}
|
||||||
|
return xs[n-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
println(Primes(3))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// 5
|
||||||
11
_test/str4.go
Normal file
11
_test/str4.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "unicode/utf8"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r, _ := utf8.DecodeRuneInString("Hello")
|
||||||
|
println(r < utf8.RuneSelf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// true
|
||||||
19
_test/struct29.go
Normal file
19
_test/struct29.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
type T1 struct {
|
||||||
|
A []T2
|
||||||
|
B []T2
|
||||||
|
}
|
||||||
|
|
||||||
|
type T2 struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
var t = T1{}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
println("ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// ok
|
||||||
19
_test/struct30.go
Normal file
19
_test/struct30.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
type T1 struct {
|
||||||
|
A []T2
|
||||||
|
M map[uint64]T2
|
||||||
|
}
|
||||||
|
|
||||||
|
type T2 struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
var t = T1{}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
println("ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// ok
|
||||||
15
_test/time11.go
Normal file
15
_test/time11.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const df = time.Minute * 30
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Printf("df: %v %T\n", df, df)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// df: 30m0s time.Duration
|
||||||
11
_test/var10.go
Normal file
11
_test/var10.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
var _ = true
|
||||||
|
var _ = "hello"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
println("hello")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// hello
|
||||||
10
_test/var11.go
Normal file
10
_test/var11.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
var a, _, _, b = 1, true, "foo", 2
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
println(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// 1 2
|
||||||
@@ -8,7 +8,7 @@ If invoked with no arguments, it processes the standard input
|
|||||||
in a Read-Eval-Print-Loop. A prompt is displayed if standard input
|
in a Read-Eval-Print-Loop. A prompt is displayed if standard input
|
||||||
is a terminal.
|
is a terminal.
|
||||||
|
|
||||||
Given a file, it operates on that file. if the first line starts with
|
Given a file, it operates on that file. If the first line starts with
|
||||||
"#!/usr/bin/env yaegi", and the file has exec permission, then the file
|
"#!/usr/bin/env yaegi", and the file has exec permission, then the file
|
||||||
can be invoked directly from the shell.
|
can be invoked directly from the shell.
|
||||||
|
|
||||||
|
|||||||
@@ -352,6 +352,30 @@ func {{$name}}(n *node) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{range $name, $op := .Unary}}
|
||||||
|
func {{$name}}Const(n *node) {
|
||||||
|
t := n.typ.rtype
|
||||||
|
v := n.child[0].rval
|
||||||
|
n.rval = reflect.New(t).Elem()
|
||||||
|
|
||||||
|
{{- if $op.Bool}}
|
||||||
|
n.rval.SetBool({{$op.Name}} v.Bool())
|
||||||
|
{{- else}}
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
n.rval.SetInt({{$op.Name}} v.Int())
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
n.rval.SetUint({{$op.Name}} v.Uint())
|
||||||
|
{{- if $op.Float}}
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
n.rval.SetFloat({{$op.Name}} v.Float())
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
n.rval.SetComplex({{$op.Name}} v.Complex())
|
||||||
|
{{- end}}
|
||||||
|
}
|
||||||
|
{{- end}}
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
{{range $name, $op := .Comparison}}
|
{{range $name, $op := .Comparison}}
|
||||||
func {{$name}}(n *node) {
|
func {{$name}}(n *node) {
|
||||||
tnext := getExec(n.tnext)
|
tnext := getExec(n.tnext)
|
||||||
@@ -793,6 +817,7 @@ type Op struct {
|
|||||||
Float bool // true if operator applies to float
|
Float bool // true if operator applies to float
|
||||||
Complex bool // true if operator applies to complex
|
Complex bool // true if operator applies to complex
|
||||||
Shift bool // true if operator is a shift operation
|
Shift bool // true if operator is a shift operation
|
||||||
|
Bool bool // true if operator applies to bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -805,17 +830,17 @@ func main() {
|
|||||||
b := &bytes.Buffer{}
|
b := &bytes.Buffer{}
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"Arithmetic": map[string]Op{
|
"Arithmetic": map[string]Op{
|
||||||
"add": {"+", true, true, true, false},
|
"add": {"+", true, true, true, false, false},
|
||||||
"sub": {"-", false, true, true, false},
|
"sub": {"-", false, true, true, false, false},
|
||||||
"mul": {"*", false, true, true, false},
|
"mul": {"*", false, true, true, false, false},
|
||||||
"quo": {"/", false, true, true, false},
|
"quo": {"/", false, true, true, false, false},
|
||||||
"rem": {"%", false, false, false, false},
|
"rem": {"%", false, false, false, false, false},
|
||||||
"shl": {"<<", false, false, false, true},
|
"shl": {"<<", false, false, false, true, false},
|
||||||
"shr": {">>", false, false, false, true},
|
"shr": {">>", false, false, false, true, false},
|
||||||
"and": {"&", false, false, false, false},
|
"and": {"&", false, false, false, false, false},
|
||||||
"or": {"|", false, false, false, false},
|
"or": {"|", false, false, false, false, false},
|
||||||
"xor": {"^", false, false, false, false},
|
"xor": {"^", false, false, false, false, false},
|
||||||
"andNot": {"&^", false, false, false, false},
|
"andNot": {"&^", false, false, false, false, false},
|
||||||
},
|
},
|
||||||
"IncDec": map[string]Op{
|
"IncDec": map[string]Op{
|
||||||
"inc": {Name: "+"},
|
"inc": {Name: "+"},
|
||||||
@@ -829,6 +854,12 @@ func main() {
|
|||||||
"lowerEqual": {Name: "<=", Complex: false},
|
"lowerEqual": {Name: "<=", Complex: false},
|
||||||
"notEqual": {Name: "!=", Complex: true},
|
"notEqual": {Name: "!=", Complex: true},
|
||||||
},
|
},
|
||||||
|
"Unary": map[string]Op{
|
||||||
|
"not": {Name: "!", Float: false, Bool: true},
|
||||||
|
"neg": {Name: "-", Float: true, Bool: false},
|
||||||
|
"pos": {Name: "+", Float: true, Bool: false},
|
||||||
|
"bitNot": {Name: "^", Float: false, Bool: false},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if err = parse.Execute(b, data); err != nil {
|
if err = parse.Execute(b, data); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ const (
|
|||||||
aAndAssign
|
aAndAssign
|
||||||
aAndNot
|
aAndNot
|
||||||
aAndNotAssign
|
aAndNotAssign
|
||||||
|
aBitNot
|
||||||
aCall
|
aCall
|
||||||
aCase
|
aCase
|
||||||
aCompositeLit
|
aCompositeLit
|
||||||
@@ -217,11 +218,12 @@ const (
|
|||||||
aMethod
|
aMethod
|
||||||
aMul
|
aMul
|
||||||
aMulAssign
|
aMulAssign
|
||||||
aNegate
|
aNeg
|
||||||
aNot
|
aNot
|
||||||
aNotEqual
|
aNotEqual
|
||||||
aOr
|
aOr
|
||||||
aOrAssign
|
aOrAssign
|
||||||
|
aPos
|
||||||
aQuo
|
aQuo
|
||||||
aQuoAssign
|
aQuoAssign
|
||||||
aRange
|
aRange
|
||||||
@@ -255,6 +257,7 @@ var actions = [...]string{
|
|||||||
aAndAssign: "&=",
|
aAndAssign: "&=",
|
||||||
aAndNot: "&^",
|
aAndNot: "&^",
|
||||||
aAndNotAssign: "&^=",
|
aAndNotAssign: "&^=",
|
||||||
|
aBitNot: "^",
|
||||||
aCall: "call",
|
aCall: "call",
|
||||||
aCase: "case",
|
aCase: "case",
|
||||||
aCompositeLit: "compositeLit",
|
aCompositeLit: "compositeLit",
|
||||||
@@ -271,11 +274,12 @@ var actions = [...]string{
|
|||||||
aMethod: "Method",
|
aMethod: "Method",
|
||||||
aMul: "*",
|
aMul: "*",
|
||||||
aMulAssign: "*=",
|
aMulAssign: "*=",
|
||||||
aNegate: "-",
|
aNeg: "-",
|
||||||
aNot: "!",
|
aNot: "!",
|
||||||
aNotEqual: "!=",
|
aNotEqual: "!=",
|
||||||
aOr: "|",
|
aOr: "|",
|
||||||
aOrAssign: "|=",
|
aOrAssign: "|=",
|
||||||
|
aPos: "+",
|
||||||
aQuo: "/",
|
aQuo: "/",
|
||||||
aQuoAssign: "/=",
|
aQuoAssign: "/=",
|
||||||
aRange: "range",
|
aRange: "range",
|
||||||
@@ -771,6 +775,8 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
|
|||||||
var kind = unaryExpr
|
var kind = unaryExpr
|
||||||
var act action
|
var act action
|
||||||
switch a.Op {
|
switch a.Op {
|
||||||
|
case token.ADD:
|
||||||
|
act = aPos
|
||||||
case token.AND:
|
case token.AND:
|
||||||
kind = addressExpr
|
kind = addressExpr
|
||||||
act = aAddr
|
act = aAddr
|
||||||
@@ -779,7 +785,9 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
|
|||||||
case token.NOT:
|
case token.NOT:
|
||||||
act = aNot
|
act = aNot
|
||||||
case token.SUB:
|
case token.SUB:
|
||||||
act = aNegate
|
act = aNeg
|
||||||
|
case token.XOR:
|
||||||
|
act = aBitNot
|
||||||
}
|
}
|
||||||
st.push(addChild(&root, anc, pos, kind, act), nod)
|
st.push(addChild(&root, anc, pos, kind, act), nod)
|
||||||
|
|
||||||
|
|||||||
154
interp/cfg.go
154
interp/cfg.go
@@ -24,6 +24,10 @@ var constOp = map[action]func(*node){
|
|||||||
aShr: shrConst,
|
aShr: shrConst,
|
||||||
aAndNot: andNotConst,
|
aAndNot: andNotConst,
|
||||||
aXor: xorConst,
|
aXor: xorConst,
|
||||||
|
aNot: notConst,
|
||||||
|
aBitNot: bitNotConst,
|
||||||
|
aNeg: negConst,
|
||||||
|
aPos: posConst,
|
||||||
}
|
}
|
||||||
|
|
||||||
var constBltn = map[string]func(*node){
|
var constBltn = map[string]func(*node){
|
||||||
@@ -38,9 +42,8 @@ var identifier = regexp.MustCompile(`([\pL_][\pL_\d]*)$`)
|
|||||||
// and pre-compute frame sizes and indexes for all un-named (temporary) and named
|
// and pre-compute frame sizes and indexes for all un-named (temporary) and named
|
||||||
// variables. A list of nodes of init functions is returned.
|
// 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) ([]*node, error) {
|
func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
|
||||||
sc, pkgName := interp.initScopePkg(root)
|
sc := interp.initScopePkg(pkgID)
|
||||||
var loop, loopRestart *node
|
|
||||||
var initNodes []*node
|
var initNodes []*node
|
||||||
var iotaValue int
|
var iotaValue int
|
||||||
var err error
|
var err error
|
||||||
@@ -232,12 +235,12 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case forStmt0, forRangeStmt:
|
case forStmt0, forRangeStmt:
|
||||||
loop, loopRestart = n, n.child[0]
|
|
||||||
sc = sc.pushBloc()
|
sc = sc.pushBloc()
|
||||||
|
sc.loop, sc.loopRestart = n, n.child[0]
|
||||||
|
|
||||||
case forStmt1, forStmt2, forStmt3, forStmt3a, forStmt4:
|
case forStmt1, forStmt2, forStmt3, forStmt3a, forStmt4:
|
||||||
loop, loopRestart = n, n.lastChild()
|
|
||||||
sc = sc.pushBloc()
|
sc = sc.pushBloc()
|
||||||
|
sc.loop, sc.loopRestart = n, n.lastChild()
|
||||||
|
|
||||||
case funcLit:
|
case funcLit:
|
||||||
n.typ = nil // to force nodeType to recompute the type
|
n.typ = nil // to force nodeType to recompute the type
|
||||||
@@ -309,7 +312,7 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
c[i], c[l] = c[l], c[i]
|
c[i], c[l] = c[l], c[i]
|
||||||
}
|
}
|
||||||
sc = sc.pushBloc()
|
sc = sc.pushBloc()
|
||||||
loop = n
|
sc.loop = n
|
||||||
|
|
||||||
case importSpec:
|
case importSpec:
|
||||||
var name, ipath string
|
var name, ipath string
|
||||||
@@ -407,7 +410,11 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
if src.typ, err = nodeType(interp, sc, src); err != nil {
|
if src.typ, err = nodeType(interp, sc, src); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dest.typ = src.typ
|
if src.typ.isBinMethod {
|
||||||
|
dest.typ = &itype{cat: valueT, rtype: src.typ.methodCallType()}
|
||||||
|
} else {
|
||||||
|
dest.typ = src.typ
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if dest.typ.sizedef {
|
if dest.typ.sizedef {
|
||||||
dest.typ.size = arrayTypeLen(src)
|
dest.typ.size = arrayTypeLen(src)
|
||||||
@@ -602,8 +609,6 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
constOp[n.action](n)
|
constOp[n.action](n)
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
//case n.typ != nil && n.typ.cat == BoolT && isAncBranch(n):
|
|
||||||
// n.findex = -1
|
|
||||||
case n.rval.IsValid():
|
case n.rval.IsValid():
|
||||||
n.gen = nop
|
n.gen = nop
|
||||||
n.findex = -1
|
n.findex = -1
|
||||||
@@ -696,14 +701,14 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
if len(n.child) > 0 {
|
if len(n.child) > 0 {
|
||||||
gotoLabel(n.sym)
|
gotoLabel(n.sym)
|
||||||
} else {
|
} else {
|
||||||
n.tnext = loop
|
n.tnext = sc.loop
|
||||||
}
|
}
|
||||||
|
|
||||||
case continueStmt:
|
case continueStmt:
|
||||||
if len(n.child) > 0 {
|
if len(n.child) > 0 {
|
||||||
gotoLabel(n.sym)
|
gotoLabel(n.sym)
|
||||||
} else {
|
} else {
|
||||||
n.tnext = loopRestart
|
n.tnext = sc.loopRestart
|
||||||
}
|
}
|
||||||
|
|
||||||
case gotoStmt:
|
case gotoStmt:
|
||||||
@@ -827,7 +832,6 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
body := n.child[0]
|
body := n.child[0]
|
||||||
n.start = body.start
|
n.start = body.start
|
||||||
body.tnext = n.start
|
body.tnext = n.start
|
||||||
loop, loopRestart = nil, nil
|
|
||||||
sc = sc.pop()
|
sc = sc.pop()
|
||||||
|
|
||||||
case forStmt1: // for cond {}
|
case forStmt1: // for cond {}
|
||||||
@@ -839,7 +843,6 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
cond.tnext = body.start
|
cond.tnext = body.start
|
||||||
cond.fnext = n
|
cond.fnext = n
|
||||||
body.tnext = cond.start
|
body.tnext = cond.start
|
||||||
loop, loopRestart = nil, nil
|
|
||||||
sc = sc.pop()
|
sc = sc.pop()
|
||||||
|
|
||||||
case forStmt2: // for init; cond; {}
|
case forStmt2: // for init; cond; {}
|
||||||
@@ -852,7 +855,6 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
cond.tnext = body.start
|
cond.tnext = body.start
|
||||||
cond.fnext = n
|
cond.fnext = n
|
||||||
body.tnext = cond.start
|
body.tnext = cond.start
|
||||||
loop, loopRestart = nil, nil
|
|
||||||
sc = sc.pop()
|
sc = sc.pop()
|
||||||
|
|
||||||
case forStmt3: // for ; cond; post {}
|
case forStmt3: // for ; cond; post {}
|
||||||
@@ -865,7 +867,6 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
cond.fnext = n
|
cond.fnext = n
|
||||||
body.tnext = post.start
|
body.tnext = post.start
|
||||||
post.tnext = cond.start
|
post.tnext = cond.start
|
||||||
loop, loopRestart = nil, nil
|
|
||||||
sc = sc.pop()
|
sc = sc.pop()
|
||||||
|
|
||||||
case forStmt3a: // for int; ; post {}
|
case forStmt3a: // for int; ; post {}
|
||||||
@@ -874,7 +875,6 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
init.tnext = body.start
|
init.tnext = body.start
|
||||||
body.tnext = post.start
|
body.tnext = post.start
|
||||||
post.tnext = body.start
|
post.tnext = body.start
|
||||||
loop, loopRestart = nil, nil
|
|
||||||
sc = sc.pop()
|
sc = sc.pop()
|
||||||
|
|
||||||
case forStmt4: // for init; cond; post {}
|
case forStmt4: // for init; cond; post {}
|
||||||
@@ -888,11 +888,9 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
cond.fnext = n
|
cond.fnext = n
|
||||||
body.tnext = post.start
|
body.tnext = post.start
|
||||||
post.tnext = cond.start
|
post.tnext = cond.start
|
||||||
loop, loopRestart = nil, nil
|
|
||||||
sc = sc.pop()
|
sc = sc.pop()
|
||||||
|
|
||||||
case forRangeStmt:
|
case forRangeStmt:
|
||||||
loop, loopRestart = nil, nil
|
|
||||||
n.start = n.child[0].start
|
n.start = n.child[0].start
|
||||||
n.child[0].fnext = n
|
n.child[0].fnext = n
|
||||||
sc = sc.pop()
|
sc = sc.pop()
|
||||||
@@ -903,10 +901,10 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
sc = sc.pop()
|
sc = sc.pop()
|
||||||
funcName := n.child[1].ident
|
funcName := n.child[1].ident
|
||||||
if !isMethod(n) {
|
if !isMethod(n) {
|
||||||
interp.scopes[pkgName].sym[funcName].index = -1 // to force value to n.val
|
interp.scopes[pkgID].sym[funcName].index = -1 // to force value to n.val
|
||||||
interp.scopes[pkgName].sym[funcName].typ = n.typ
|
interp.scopes[pkgID].sym[funcName].typ = n.typ
|
||||||
interp.scopes[pkgName].sym[funcName].kind = funcSym
|
interp.scopes[pkgID].sym[funcName].kind = funcSym
|
||||||
interp.scopes[pkgName].sym[funcName].node = n
|
interp.scopes[pkgID].sym[funcName].node = n
|
||||||
}
|
}
|
||||||
|
|
||||||
case funcLit:
|
case funcLit:
|
||||||
@@ -1021,6 +1019,9 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
n.child[1].tnext = n
|
n.child[1].tnext = n
|
||||||
n.typ = n.child[0].typ
|
n.typ = n.child[0].typ
|
||||||
n.findex = sc.add(n.typ)
|
n.findex = sc.add(n.typ)
|
||||||
|
if n.start.action == aNop {
|
||||||
|
n.start.gen = branch
|
||||||
|
}
|
||||||
|
|
||||||
case lorExpr:
|
case lorExpr:
|
||||||
n.start = n.child[0].start
|
n.start = n.child[0].start
|
||||||
@@ -1029,6 +1030,9 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
n.child[1].tnext = n
|
n.child[1].tnext = n
|
||||||
n.typ = n.child[0].typ
|
n.typ = n.child[0].typ
|
||||||
n.findex = sc.add(n.typ)
|
n.findex = sc.add(n.typ)
|
||||||
|
if n.start.action == aNop {
|
||||||
|
n.start.gen = branch
|
||||||
|
}
|
||||||
|
|
||||||
case parenExpr:
|
case parenExpr:
|
||||||
wireChild(n)
|
wireChild(n)
|
||||||
@@ -1068,7 +1072,7 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
// nil: Set node value to zero of return type
|
// nil: Set node value to zero of return type
|
||||||
f := sc.def
|
f := sc.def
|
||||||
var typ *itype
|
var typ *itype
|
||||||
if typ, err = nodeType(interp, sc, f.child[2].child[1].child[i].lastChild()); err != nil {
|
if typ, err = nodeType(interp, sc, f.child[2].child[1].fieldType(i)); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if typ.cat == funcT {
|
if typ.cat == funcT {
|
||||||
@@ -1097,7 +1101,7 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
n.val = method.Index
|
n.val = method.Index
|
||||||
n.gen = getIndexBinMethod
|
n.gen = getIndexBinMethod
|
||||||
n.recv = &receiver{node: n.child[0]}
|
n.recv = &receiver{node: n.child[0]}
|
||||||
n.typ = &itype{cat: valueT, rtype: method.Type}
|
n.typ = &itype{cat: valueT, rtype: method.Type, isBinMethod: true}
|
||||||
case n.typ.rtype.Kind() == reflect.Ptr:
|
case n.typ.rtype.Kind() == reflect.Ptr:
|
||||||
if field, ok := n.typ.rtype.Elem().FieldByName(n.child[1].ident); ok {
|
if field, ok := n.typ.rtype.Elem().FieldByName(n.child[1].ident); ok {
|
||||||
n.typ = &itype{cat: valueT, rtype: field.Type}
|
n.typ = &itype{cat: valueT, rtype: field.Type}
|
||||||
@@ -1155,7 +1159,7 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
n.typ = &itype{cat: valueT, rtype: s.Type().Elem()}
|
n.typ = &itype{cat: valueT, rtype: s.Type().Elem()}
|
||||||
} else {
|
} else {
|
||||||
n.kind = rvalueExpr
|
n.kind = rvalueExpr
|
||||||
n.typ = &itype{cat: valueT, rtype: s.Type()}
|
n.typ = &itype{cat: valueT, rtype: s.Type(), untyped: isValueUntyped(s)}
|
||||||
n.rval = s
|
n.rval = s
|
||||||
}
|
}
|
||||||
n.gen = nop
|
n.gen = nop
|
||||||
@@ -1312,7 +1316,6 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
n.start = sbn.start
|
n.start = sbn.start
|
||||||
}
|
}
|
||||||
sc = sc.pop()
|
sc = sc.pop()
|
||||||
loop = nil
|
|
||||||
|
|
||||||
case switchIfStmt: // like an if-else chain
|
case switchIfStmt: // like an if-else chain
|
||||||
sbn := n.lastChild() // switch block node
|
sbn := n.lastChild() // switch block node
|
||||||
@@ -1349,7 +1352,6 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
n.start = sbn.start
|
n.start = sbn.start
|
||||||
}
|
}
|
||||||
sc = sc.pop()
|
sc = sc.pop()
|
||||||
loop = nil
|
|
||||||
|
|
||||||
case typeAssertExpr:
|
case typeAssertExpr:
|
||||||
if len(n.child) > 1 {
|
if len(n.child) > 1 {
|
||||||
@@ -1377,16 +1379,60 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
case unaryExpr:
|
case unaryExpr:
|
||||||
wireChild(n)
|
wireChild(n)
|
||||||
n.typ = n.child[0].typ
|
n.typ = n.child[0].typ
|
||||||
if n.action == aRecv {
|
switch n.action {
|
||||||
|
case aRecv:
|
||||||
// Channel receive operation: set type to the channel data type
|
// Channel receive operation: set type to the channel data type
|
||||||
if n.typ.cat == valueT {
|
if n.typ.cat == valueT {
|
||||||
n.typ = &itype{cat: valueT, rtype: n.typ.rtype.Elem()}
|
n.typ = &itype{cat: valueT, rtype: n.typ.rtype.Elem()}
|
||||||
} else {
|
} else {
|
||||||
n.typ = n.typ.val
|
n.typ = n.typ.val
|
||||||
}
|
}
|
||||||
|
case aBitNot:
|
||||||
|
if !isInt(n.typ.TypeOf()) {
|
||||||
|
err = n.cfgErrorf("illegal operand type for '^' operator")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case aNot:
|
||||||
|
if !isBool(n.typ) {
|
||||||
|
err = n.cfgErrorf("illegal operand type for '!' operator")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case aNeg, aPos:
|
||||||
|
if !isNumber(n.typ.TypeOf()) {
|
||||||
|
err = n.cfgErrorf("illegal operand type for '%v' operator", n.action)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// TODO: Optimisation: avoid allocation if boolean branch op (i.e. '!' in an 'if' expr)
|
// TODO: Optimisation: avoid allocation if boolean branch op (i.e. '!' in an 'if' expr)
|
||||||
n.findex = sc.add(n.typ)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n.typ.TypeOf() // init reflect type
|
||||||
|
constOp[n.action](n)
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case n.rval.IsValid():
|
||||||
|
n.gen = nop
|
||||||
|
n.findex = -1
|
||||||
|
case n.anc.kind == assignStmt && n.anc.action == aAssign:
|
||||||
|
dest := n.anc.child[childPos(n)-n.anc.nright]
|
||||||
|
n.typ = dest.typ
|
||||||
|
n.findex = dest.findex
|
||||||
|
case n.anc.kind == returnStmt:
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n.findex = sc.add(n.typ)
|
||||||
|
}
|
||||||
|
|
||||||
case valueSpec:
|
case valueSpec:
|
||||||
n.gen = reset
|
n.gen = reset
|
||||||
@@ -1397,8 +1443,14 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, c := range n.child[:l] {
|
for _, c := range n.child[:l] {
|
||||||
index := sc.add(n.typ)
|
var index int
|
||||||
sc.sym[c.ident] = &symbol{index: index, kind: varSym, typ: n.typ}
|
if sc.global {
|
||||||
|
// Global object allocation is already performed in GTA.
|
||||||
|
index = sc.sym[c.ident].index
|
||||||
|
} else {
|
||||||
|
index = sc.add(n.typ)
|
||||||
|
sc.sym[c.ident] = &symbol{index: index, kind: varSym, typ: n.typ}
|
||||||
|
}
|
||||||
c.typ = n.typ
|
c.typ = n.typ
|
||||||
c.findex = index
|
c.findex = index
|
||||||
}
|
}
|
||||||
@@ -1643,6 +1695,29 @@ func (n *node) isNatural() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
cl := len(n.child[i].child)
|
||||||
|
if cl < 2 {
|
||||||
|
if k == m {
|
||||||
|
return n.child[i].lastChild()
|
||||||
|
}
|
||||||
|
k++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for j := 0; j < cl-1; j++ {
|
||||||
|
if k == m {
|
||||||
|
return n.child[i].lastChild()
|
||||||
|
}
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 (n *node) lastChild() *node { return n.child[len(n.child)-1] }
|
||||||
|
|
||||||
@@ -1718,13 +1793,6 @@ func getExec(n *node) bltn {
|
|||||||
return n.exec
|
return n.exec
|
||||||
}
|
}
|
||||||
|
|
||||||
func fileNode(n *node) *node {
|
|
||||||
if n == nil || n.kind == fileStmt {
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
return fileNode(n.anc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// setExec recursively sets the node exec builtin function by walking the CFG
|
// setExec recursively sets the node exec builtin function by walking the CFG
|
||||||
// from the entry point (first node to exec).
|
// from the entry point (first node to exec).
|
||||||
func setExec(n *node) {
|
func setExec(n *node) {
|
||||||
@@ -1825,3 +1893,13 @@ func arrayTypeLen(n *node) int {
|
|||||||
}
|
}
|
||||||
return max + 1
|
return max + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
return t.String() == t.Kind().String()
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,18 @@ Package interp provides a complete Go interpreter.
|
|||||||
For the Go language itself, refer to the official Go specification
|
For the Go language itself, refer to the official Go specification
|
||||||
https://golang.org/ref/spec.
|
https://golang.org/ref/spec.
|
||||||
|
|
||||||
|
Importing packages
|
||||||
|
|
||||||
|
Packages can be imported in source or binary form, using the standard
|
||||||
|
Go import statement. In source form, packages are searched first in the
|
||||||
|
vendor directory, the preferred way to store source dependencies. If not
|
||||||
|
found in vendor, sources modules will be searched in GOPATH. Go modules
|
||||||
|
are not supported yet by yaegi.
|
||||||
|
|
||||||
|
Binary form packages are compiled and linked with the interpreter
|
||||||
|
executable, and exposed to scripts with the Use method. The goexports
|
||||||
|
command can be used to generate package wrappers.
|
||||||
|
|
||||||
Custom build tags
|
Custom build tags
|
||||||
|
|
||||||
Custom build tags allow to control which files in imported source
|
Custom build tags allow to control which files in imported source
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func (n *node) astDot(out io.Writer, name string) {
|
|||||||
func (n *node) cfgDot(out io.Writer) {
|
func (n *node) cfgDot(out io.Writer) {
|
||||||
fmt.Fprintf(out, "digraph cfg {\n")
|
fmt.Fprintf(out, "digraph cfg {\n")
|
||||||
n.Walk(nil, func(n *node) {
|
n.Walk(nil, func(n *node) {
|
||||||
if n.kind == basicLit || n.kind == identExpr || n.tnext == nil {
|
if n.kind == basicLit || n.tnext == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var label string
|
var label string
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
// variables and functions symbols at package level, prior to CFG.
|
// variables and functions symbols at package level, prior to CFG.
|
||||||
// All function bodies are skipped. GTA is necessary to handle out of
|
// All function bodies are skipped. GTA is necessary to handle out of
|
||||||
// order declarations and multiple source files packages.
|
// order declarations and multiple source files packages.
|
||||||
func (interp *Interpreter) gta(root *node, rpath string) ([]*node, error) {
|
func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error) {
|
||||||
sc, _ := interp.initScopePkg(root)
|
sc := interp.initScopePkg(pkgID)
|
||||||
var err error
|
var err error
|
||||||
var iotaValue int
|
var iotaValue int
|
||||||
var revisit []*node
|
var revisit []*node
|
||||||
@@ -30,6 +30,7 @@ func (interp *Interpreter) gta(root *node, rpath string) ([]*node, error) {
|
|||||||
case defineStmt:
|
case defineStmt:
|
||||||
var atyp *itype
|
var atyp *itype
|
||||||
if n.nleft+n.nright < len(n.child) {
|
if n.nleft+n.nright < len(n.child) {
|
||||||
|
// Type is declared explicitly in the assign expression.
|
||||||
if atyp, err = nodeType(interp, sc, n.child[n.nleft]); err != nil {
|
if atyp, err = nodeType(interp, sc, n.child[n.nleft]); err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -51,7 +52,7 @@ func (interp *Interpreter) gta(root *node, rpath string) ([]*node, error) {
|
|||||||
val = src.rval
|
val = src.rval
|
||||||
}
|
}
|
||||||
if typ.incomplete {
|
if typ.incomplete {
|
||||||
// Come back when type is known
|
// Come back when type is known.
|
||||||
revisit = append(revisit, n)
|
revisit = append(revisit, n)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -59,6 +60,9 @@ func (interp *Interpreter) gta(root *node, rpath string) ([]*node, error) {
|
|||||||
err = n.cfgErrorf("use of untyped nil")
|
err = n.cfgErrorf("use of untyped nil")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if typ.isBinMethod {
|
||||||
|
typ = &itype{cat: valueT, rtype: typ.methodCallType(), isBinMethod: true}
|
||||||
|
}
|
||||||
sc.sym[dest.ident] = &symbol{kind: varSym, global: true, index: sc.add(typ), typ: typ, rval: val}
|
sc.sym[dest.ident] = &symbol{kind: varSym, global: true, index: sc.add(typ), typ: typ, rval: val}
|
||||||
if n.anc.kind == constDecl {
|
if n.anc.kind == constDecl {
|
||||||
sc.sym[dest.ident].kind = constSym
|
sc.sym[dest.ident].kind = constSym
|
||||||
@@ -76,6 +80,11 @@ func (interp *Interpreter) gta(root *node, rpath string) ([]*node, error) {
|
|||||||
if n.typ, err = nodeType(interp, sc, n.child[l]); err != nil {
|
if n.typ, err = nodeType(interp, sc, n.child[l]); err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if n.typ.incomplete {
|
||||||
|
// Come back when type is known.
|
||||||
|
revisit = append(revisit, n)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, c := range n.child[:l] {
|
for _, c := range n.child[:l] {
|
||||||
sc.sym[c.ident] = &symbol{index: sc.add(n.typ), kind: varSym, global: true, typ: n.typ}
|
sc.sym[c.ident] = &symbol{index: sc.add(n.typ), kind: varSym, global: true, typ: n.typ}
|
||||||
@@ -142,7 +151,7 @@ func (interp *Interpreter) gta(root *node, rpath string) ([]*node, error) {
|
|||||||
default: // import symbols in package namespace
|
default: // import symbols in package namespace
|
||||||
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: binPkgT, path: ipath}}
|
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: binPkgT, path: ipath}}
|
||||||
}
|
}
|
||||||
} else if err = interp.importSrc(rpath, ipath, name); err == nil {
|
} else if err = interp.importSrc(rpath, ipath); err == nil {
|
||||||
sc.types = interp.universe.types
|
sc.types = interp.universe.types
|
||||||
switch name {
|
switch name {
|
||||||
case "_": // no import of symbols
|
case "_": // no import of symbols
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ func (interp *Interpreter) resizeFrame() {
|
|||||||
func (interp *Interpreter) main() *node {
|
func (interp *Interpreter) main() *node {
|
||||||
interp.mutex.RLock()
|
interp.mutex.RLock()
|
||||||
defer interp.mutex.RUnlock()
|
defer interp.mutex.RUnlock()
|
||||||
if m, ok := interp.scopes[mainID]; ok && m.sym[mainID] != nil {
|
if m, ok := interp.scopes[interp.Name]; ok && m.sym[mainID] != nil {
|
||||||
return m.sym[mainID].node
|
return m.sym[mainID].node
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -308,18 +308,18 @@ func (interp *Interpreter) Eval(src string) (reflect.Value, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Global type analysis
|
// Global type analysis
|
||||||
revisit, err := interp.gta(root, pkgName)
|
revisit, err := interp.gta(root, pkgName, interp.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
for _, n := range revisit {
|
for _, n := range revisit {
|
||||||
if _, err = interp.gta(n, pkgName); err != nil {
|
if _, err = interp.gta(n, pkgName, interp.Name); err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Annotate AST with CFG infos
|
// Annotate AST with CFG infos
|
||||||
initNodes, err := interp.cfg(root)
|
initNodes, err := interp.cfg(root, interp.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
@@ -336,7 +336,7 @@ func (interp *Interpreter) Eval(src string) (reflect.Value, error) {
|
|||||||
interp.mutex.Lock()
|
interp.mutex.Lock()
|
||||||
if interp.universe.sym[pkgName] == nil {
|
if interp.universe.sym[pkgName] == nil {
|
||||||
// Make the package visible under a path identical to its name
|
// Make the package visible under a path identical to its name
|
||||||
interp.srcPkg[pkgName] = interp.scopes[pkgName].sym
|
interp.srcPkg[pkgName] = interp.scopes[interp.Name].sym
|
||||||
interp.universe.sym[pkgName] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT, path: pkgName}}
|
interp.universe.sym[pkgName] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT, path: pkgName}}
|
||||||
}
|
}
|
||||||
interp.mutex.Unlock()
|
interp.mutex.Unlock()
|
||||||
|
|||||||
@@ -47,11 +47,17 @@ func TestEvalArithmetic(t *testing.T) {
|
|||||||
{desc: "shl_IN", src: "1 << -1", err: "1:28: illegal operand types for '<<' operator"},
|
{desc: "shl_IN", src: "1 << -1", err: "1:28: illegal operand types for '<<' operator"},
|
||||||
{desc: "shl_IF", src: "1 << 1.0", res: "2"},
|
{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: illegal operand types for '<<' operator"},
|
||||||
{desc: "shl_IF", src: "1.0 << 1", res: "2"},
|
{desc: "shl_IF2", src: "1.0 << 1", res: "2"},
|
||||||
{desc: "shr_II", src: "1 >> 8", res: "0"},
|
{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: illegal operand types for '>>' operator"},
|
||||||
{desc: "shr_IF", src: "1 >> 1.0", res: "0"},
|
{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: illegal operand types for '>>' operator"},
|
||||||
|
{desc: "neg_I", src: "-2", res: "-2"},
|
||||||
|
{desc: "pos_I", src: "+2", res: "2"},
|
||||||
|
{desc: "bitnot_I", src: "^2", res: "-3"},
|
||||||
|
{desc: "bitnot_F", src: "^0.2", err: "1:28: illegal operand type for '^' operator"},
|
||||||
|
{desc: "not_B", src: "!false", res: "true"},
|
||||||
|
{desc: "not_I", src: "!0", err: "1:28: illegal operand type for '!' operator"},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
51
interp/op.go
51
interp/op.go
@@ -1864,6 +1864,57 @@ func inc(n *node) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func bitNotConst(n *node) {
|
||||||
|
t := n.typ.rtype
|
||||||
|
v := n.child[0].rval
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func negConst(n *node) {
|
||||||
|
t := n.typ.rtype
|
||||||
|
v := n.child[0].rval
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func notConst(n *node) {
|
||||||
|
t := n.typ.rtype
|
||||||
|
v := n.child[0].rval
|
||||||
|
n.rval = reflect.New(t).Elem()
|
||||||
|
n.rval.SetBool(!v.Bool())
|
||||||
|
}
|
||||||
|
|
||||||
|
func posConst(n *node) {
|
||||||
|
t := n.typ.rtype
|
||||||
|
v := n.child[0].rval
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func equal(n *node) {
|
func equal(n *node) {
|
||||||
tnext := getExec(n.tnext)
|
tnext := getExec(n.tnext)
|
||||||
dest := genValue(n)
|
dest := genValue(n)
|
||||||
|
|||||||
108
interp/run.go
108
interp/run.go
@@ -24,6 +24,7 @@ var builtin = [...]bltnGenerator{
|
|||||||
aAndAssign: andAssign,
|
aAndAssign: andAssign,
|
||||||
aAndNot: andNot,
|
aAndNot: andNot,
|
||||||
aAndNotAssign: andNotAssign,
|
aAndNotAssign: andNotAssign,
|
||||||
|
aBitNot: bitNot,
|
||||||
aCall: call,
|
aCall: call,
|
||||||
aCase: _case,
|
aCase: _case,
|
||||||
aCompositeLit: arrayLit,
|
aCompositeLit: arrayLit,
|
||||||
@@ -40,11 +41,12 @@ var builtin = [...]bltnGenerator{
|
|||||||
aLowerEqual: lowerEqual,
|
aLowerEqual: lowerEqual,
|
||||||
aMul: mul,
|
aMul: mul,
|
||||||
aMulAssign: mulAssign,
|
aMulAssign: mulAssign,
|
||||||
aNegate: negate,
|
aNeg: neg,
|
||||||
aNot: not,
|
aNot: not,
|
||||||
aNotEqual: notEqual,
|
aNotEqual: notEqual,
|
||||||
aOr: or,
|
aOr: or,
|
||||||
aOrAssign: orAssign,
|
aOrAssign: orAssign,
|
||||||
|
aPos: pos,
|
||||||
aQuo: quo,
|
aQuo: quo,
|
||||||
aQuoAssign: quoAssign,
|
aQuoAssign: quoAssign,
|
||||||
aRange: _range,
|
aRange: _range,
|
||||||
@@ -239,7 +241,7 @@ func isRecursiveStruct(t *itype, rtype reflect.Type) bool {
|
|||||||
if t.cat == structT && rtype.Kind() == reflect.Interface {
|
if t.cat == structT && rtype.Kind() == reflect.Interface {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if t.cat == ptrT {
|
if t.cat == ptrT && t.rtype != nil {
|
||||||
return isRecursiveStruct(t.val, t.rtype.Elem())
|
return isRecursiveStruct(t.val, t.rtype.Elem())
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -291,12 +293,17 @@ func assign(n *node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if n.nleft == 1 {
|
if n.nleft == 1 {
|
||||||
if s, d, i := svalue[0], dvalue[0], ivalue[0]; i != nil {
|
switch s, d, i := svalue[0], dvalue[0], ivalue[0]; {
|
||||||
|
case n.child[0].ident == "_":
|
||||||
|
n.exec = func(f *frame) bltn {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
case i != nil:
|
||||||
n.exec = func(f *frame) bltn {
|
n.exec = func(f *frame) bltn {
|
||||||
d(f).SetMapIndex(i(f), s(f))
|
d(f).SetMapIndex(i(f), s(f))
|
||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
n.exec = func(f *frame) bltn {
|
n.exec = func(f *frame) bltn {
|
||||||
d(f).Set(s(f))
|
d(f).Set(s(f))
|
||||||
return next
|
return next
|
||||||
@@ -314,7 +321,6 @@ func assign(n *node) {
|
|||||||
default:
|
default:
|
||||||
t = typ.TypeOf()
|
t = typ.TypeOf()
|
||||||
}
|
}
|
||||||
//types[i] = n.child[sbase+i].typ.TypeOf()
|
|
||||||
types[i] = t
|
types[i] = t
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,10 +330,16 @@ func assign(n *node) {
|
|||||||
n.exec = func(f *frame) bltn {
|
n.exec = func(f *frame) bltn {
|
||||||
t := make([]reflect.Value, len(svalue))
|
t := make([]reflect.Value, len(svalue))
|
||||||
for i, s := range svalue {
|
for i, s := range svalue {
|
||||||
|
if n.child[i].ident == "_" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
t[i] = reflect.New(types[i]).Elem()
|
t[i] = reflect.New(types[i]).Elem()
|
||||||
t[i].Set(s(f))
|
t[i].Set(s(f))
|
||||||
}
|
}
|
||||||
for i, d := range dvalue {
|
for i, d := range dvalue {
|
||||||
|
if n.child[i].ident == "_" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if j := ivalue[i]; j != nil {
|
if j := ivalue[i]; j != nil {
|
||||||
d(f).SetMapIndex(j(f), t[i]) // Assign a map entry
|
d(f).SetMapIndex(j(f), t[i]) // Assign a map entry
|
||||||
} else {
|
} else {
|
||||||
@@ -340,21 +352,24 @@ func assign(n *node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func not(n *node) {
|
func not(n *node) {
|
||||||
|
dest := genValue(n)
|
||||||
value := genValue(n.child[0])
|
value := genValue(n.child[0])
|
||||||
tnext := getExec(n.tnext)
|
tnext := getExec(n.tnext)
|
||||||
|
i := n.findex
|
||||||
|
|
||||||
if n.fnext != nil {
|
if n.fnext != nil {
|
||||||
fnext := getExec(n.fnext)
|
fnext := getExec(n.fnext)
|
||||||
n.exec = func(f *frame) bltn {
|
n.exec = func(f *frame) bltn {
|
||||||
if !value(f).Bool() {
|
if !value(f).Bool() {
|
||||||
|
f.data[i].SetBool(true)
|
||||||
return tnext
|
return tnext
|
||||||
}
|
}
|
||||||
|
f.data[i].SetBool(false)
|
||||||
return fnext
|
return fnext
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
i := n.findex
|
|
||||||
n.exec = func(f *frame) bltn {
|
n.exec = func(f *frame) bltn {
|
||||||
f.data[i].SetBool(!value(f).Bool())
|
dest(f).SetBool(!value(f).Bool())
|
||||||
return tnext
|
return tnext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -658,7 +673,7 @@ func call(n *node) {
|
|||||||
values = append(values, func(f *frame) reflect.Value { return f.data[ind] })
|
values = append(values, func(f *frame) reflect.Value { return f.data[ind] })
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if c.kind == basicLit {
|
if c.kind == basicLit || c.rval.IsValid() {
|
||||||
var argType reflect.Type
|
var argType reflect.Type
|
||||||
if variadic >= 0 && i >= variadic {
|
if variadic >= 0 && i >= variadic {
|
||||||
argType = n.child[0].typ.arg[variadic].val.TypeOf()
|
argType = n.child[0].typ.arg[variadic].val.TypeOf()
|
||||||
@@ -823,7 +838,9 @@ func callBin(n *node) {
|
|||||||
// method signature obtained from reflect.Type include receiver as 1st arg, except for interface types
|
// method signature obtained from reflect.Type include receiver as 1st arg, except for interface types
|
||||||
rcvrOffset := 0
|
rcvrOffset := 0
|
||||||
if recv := n.child[0].recv; recv != nil && recv.node.typ.TypeOf().Kind() != reflect.Interface {
|
if recv := n.child[0].recv; recv != nil && recv.node.typ.TypeOf().Kind() != reflect.Interface {
|
||||||
rcvrOffset = 1
|
if funcType.NumIn() > len(child) {
|
||||||
|
rcvrOffset = 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, c := range child {
|
for i, c := range child {
|
||||||
@@ -843,7 +860,7 @@ func callBin(n *node) {
|
|||||||
values = append(values, func(f *frame) reflect.Value { return f.data[ind] })
|
values = append(values, func(f *frame) reflect.Value { return f.data[ind] })
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if c.kind == basicLit {
|
if c.kind == basicLit || c.rval.IsValid() {
|
||||||
// Convert literal value (untyped) to function argument type (if not an interface{})
|
// Convert literal value (untyped) to function argument type (if not an interface{})
|
||||||
var argType reflect.Type
|
var argType reflect.Type
|
||||||
if variadic >= 0 && i >= variadic {
|
if variadic >= 0 && i >= variadic {
|
||||||
@@ -862,7 +879,6 @@ func callBin(n *node) {
|
|||||||
case interfaceT:
|
case interfaceT:
|
||||||
values = append(values, genValueInterfaceValue(c))
|
values = append(values, genValueInterfaceValue(c))
|
||||||
default:
|
default:
|
||||||
//values = append(values, genValue(c))
|
|
||||||
values = append(values, genInterfaceWrapper(c, defType))
|
values = append(values, genInterfaceWrapper(c, defType))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1246,7 +1262,7 @@ func getIndexSeqMethod(n *node) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func negate(n *node) {
|
func neg(n *node) {
|
||||||
dest := genValue(n)
|
dest := genValue(n)
|
||||||
value := genValue(n.child[0])
|
value := genValue(n.child[0])
|
||||||
next := getExec(n.tnext)
|
next := getExec(n.tnext)
|
||||||
@@ -1270,6 +1286,37 @@ func negate(n *node) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pos(n *node) {
|
||||||
|
dest := genValue(n)
|
||||||
|
value := genValue(n.child[0])
|
||||||
|
next := getExec(n.tnext)
|
||||||
|
|
||||||
|
n.exec = func(f *frame) bltn {
|
||||||
|
dest(f).Set(value(f))
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bitNot(n *node) {
|
||||||
|
dest := genValue(n)
|
||||||
|
value := genValue(n.child[0])
|
||||||
|
next := getExec(n.tnext)
|
||||||
|
typ := n.typ.TypeOf()
|
||||||
|
|
||||||
|
switch typ.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
n.exec = func(f *frame) bltn {
|
||||||
|
dest(f).SetInt(^value(f).Int())
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
n.exec = func(f *frame) bltn {
|
||||||
|
dest(f).SetUint(^value(f).Uint())
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func land(n *node) {
|
func land(n *node) {
|
||||||
value0 := genValue(n.child[0])
|
value0 := genValue(n.child[0])
|
||||||
value1 := genValue(n.child[1])
|
value1 := genValue(n.child[1])
|
||||||
@@ -1946,7 +1993,7 @@ func _close(n *node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func _complex(n *node) {
|
func _complex(n *node) {
|
||||||
i := n.findex
|
dest := genValue(n)
|
||||||
c1, c2 := n.child[1], n.child[2]
|
c1, c2 := n.child[1], n.child[2]
|
||||||
convertLiteralValue(c1, floatType)
|
convertLiteralValue(c1, floatType)
|
||||||
convertLiteralValue(c2, floatType)
|
convertLiteralValue(c2, floatType)
|
||||||
@@ -1956,38 +2003,38 @@ func _complex(n *node) {
|
|||||||
|
|
||||||
if typ := n.typ.TypeOf(); isComplex(typ) {
|
if typ := n.typ.TypeOf(); isComplex(typ) {
|
||||||
n.exec = func(f *frame) bltn {
|
n.exec = func(f *frame) bltn {
|
||||||
f.data[i].SetComplex(complex(value0(f).Float(), value1(f).Float()))
|
dest(f).SetComplex(complex(value0(f).Float(), value1(f).Float()))
|
||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Not a complex type: ignore imaginary part
|
// Not a complex type: ignore imaginary part
|
||||||
n.exec = func(f *frame) bltn {
|
n.exec = func(f *frame) bltn {
|
||||||
f.data[i].Set(value0(f).Convert(typ))
|
dest(f).Set(value0(f).Convert(typ))
|
||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func _imag(n *node) {
|
func _imag(n *node) {
|
||||||
i := n.findex
|
dest := genValue(n)
|
||||||
convertLiteralValue(n.child[1], complexType)
|
convertLiteralValue(n.child[1], complexType)
|
||||||
value := genValue(n.child[1])
|
value := genValue(n.child[1])
|
||||||
next := getExec(n.tnext)
|
next := getExec(n.tnext)
|
||||||
|
|
||||||
n.exec = func(f *frame) bltn {
|
n.exec = func(f *frame) bltn {
|
||||||
f.data[i].SetFloat(imag(value(f).Complex()))
|
dest(f).SetFloat(imag(value(f).Complex()))
|
||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func _real(n *node) {
|
func _real(n *node) {
|
||||||
i := n.findex
|
dest := genValue(n)
|
||||||
convertLiteralValue(n.child[1], complexType)
|
convertLiteralValue(n.child[1], complexType)
|
||||||
value := genValue(n.child[1])
|
value := genValue(n.child[1])
|
||||||
next := getExec(n.tnext)
|
next := getExec(n.tnext)
|
||||||
|
|
||||||
n.exec = func(f *frame) bltn {
|
n.exec = func(f *frame) bltn {
|
||||||
f.data[i].SetFloat(real(value(f).Complex()))
|
dest(f).SetFloat(real(value(f).Complex()))
|
||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2005,12 +2052,12 @@ func _delete(n *node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func _len(n *node) {
|
func _len(n *node) {
|
||||||
i := n.findex
|
dest := genValue(n)
|
||||||
value := genValue(n.child[1])
|
value := genValue(n.child[1])
|
||||||
next := getExec(n.tnext)
|
next := getExec(n.tnext)
|
||||||
|
|
||||||
n.exec = func(f *frame) bltn {
|
n.exec = func(f *frame) bltn {
|
||||||
f.data[i].SetInt(int64(value(f).Len()))
|
dest(f).SetInt(int64(value(f).Len()))
|
||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2222,13 +2269,16 @@ func recv2(n *node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func convertLiteralValue(n *node, t reflect.Type) {
|
func convertLiteralValue(n *node, t reflect.Type) {
|
||||||
if n.kind != basicLit || t == nil || t.Kind() == reflect.Interface {
|
// Skip non-constant values, undefined target type or interface target type.
|
||||||
|
if !(n.kind == basicLit || n.rval.IsValid()) || t == nil || t.Kind() == reflect.Interface {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if n.rval.IsValid() {
|
if n.rval.IsValid() {
|
||||||
|
// Convert constant value to target type.
|
||||||
n.rval = n.rval.Convert(t)
|
n.rval = n.rval.Convert(t)
|
||||||
} else {
|
} else {
|
||||||
n.rval = reflect.New(t).Elem() // convert to type nil value
|
// Create a zero value of target type.
|
||||||
|
n.rval = reflect.New(t).Elem()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2420,19 +2470,21 @@ func isNil(n *node) {
|
|||||||
value = genValue(n.child[0])
|
value = genValue(n.child[0])
|
||||||
}
|
}
|
||||||
tnext := getExec(n.tnext)
|
tnext := getExec(n.tnext)
|
||||||
|
dest := genValue(n)
|
||||||
|
|
||||||
if n.fnext != nil {
|
if n.fnext != nil {
|
||||||
fnext := getExec(n.fnext)
|
fnext := getExec(n.fnext)
|
||||||
n.exec = func(f *frame) bltn {
|
n.exec = func(f *frame) bltn {
|
||||||
if value(f).IsNil() {
|
if value(f).IsNil() {
|
||||||
|
dest(f).SetBool(true)
|
||||||
return tnext
|
return tnext
|
||||||
}
|
}
|
||||||
|
dest(f).SetBool(false)
|
||||||
return fnext
|
return fnext
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
i := n.findex
|
|
||||||
n.exec = func(f *frame) bltn {
|
n.exec = func(f *frame) bltn {
|
||||||
f.data[i].SetBool(value(f).IsNil())
|
dest(f).SetBool(value(f).IsNil())
|
||||||
return tnext
|
return tnext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2446,19 +2498,21 @@ func isNotNil(n *node) {
|
|||||||
value = genValue(n.child[0])
|
value = genValue(n.child[0])
|
||||||
}
|
}
|
||||||
tnext := getExec(n.tnext)
|
tnext := getExec(n.tnext)
|
||||||
|
dest := genValue(n)
|
||||||
|
|
||||||
if n.fnext != nil {
|
if n.fnext != nil {
|
||||||
fnext := getExec(n.fnext)
|
fnext := getExec(n.fnext)
|
||||||
n.exec = func(f *frame) bltn {
|
n.exec = func(f *frame) bltn {
|
||||||
if value(f).IsNil() {
|
if value(f).IsNil() {
|
||||||
|
dest(f).SetBool(false)
|
||||||
return fnext
|
return fnext
|
||||||
}
|
}
|
||||||
|
dest(f).SetBool(true)
|
||||||
return tnext
|
return tnext
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
i := n.findex
|
|
||||||
n.exec = func(f *frame) bltn {
|
n.exec = func(f *frame) bltn {
|
||||||
f.data[i].SetBool(!value(f).IsNil())
|
dest(f).SetBool(!value(f).IsNil())
|
||||||
return tnext
|
return tnext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,12 +74,15 @@ type symbol struct {
|
|||||||
// execution to the index in frame, created exactly from the types layout.
|
// execution to the index in frame, created exactly from the types layout.
|
||||||
//
|
//
|
||||||
type scope struct {
|
type scope struct {
|
||||||
anc *scope // Ancestor upper scope
|
anc *scope // Ancestor upper scope
|
||||||
def *node // function definition node this scope belongs to, or nil
|
def *node // function definition node this scope belongs to, or nil
|
||||||
types []reflect.Type // Frame layout, may be shared by same level scopes
|
loop *node // loop exit node for break statement
|
||||||
level int // Frame level: number of frame indirections to access var during execution
|
loopRestart *node // loop restart node for continue statement
|
||||||
sym map[string]*symbol // Map of symbols defined in this current scope
|
pkgID string // unique id of package in which scope is defined
|
||||||
global bool // true if scope refers to global space (single frame for universe and package level scopes)
|
types []reflect.Type // Frame layout, may be shared by same level scopes
|
||||||
|
level int // Frame level: number of frame indirections to access var during execution
|
||||||
|
sym map[string]*symbol // Map of symbols defined in this current scope
|
||||||
|
global bool // true if scope refers to global space (single frame for universe and package level scopes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// push creates a new scope and chain it to the current one
|
// push creates a new scope and chain it to the current one
|
||||||
@@ -95,6 +98,8 @@ func (s *scope) push(indirect bool) *scope {
|
|||||||
sc.global = s.global
|
sc.global = s.global
|
||||||
sc.level = s.level
|
sc.level = s.level
|
||||||
}
|
}
|
||||||
|
// inherit loop state and pkgID from ancestor
|
||||||
|
sc.loop, sc.loopRestart, sc.pkgID = s.loop, s.loopRestart, s.pkgID
|
||||||
return &sc
|
return &sc
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,19 +161,15 @@ func (s *scope) add(typ *itype) (index int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (interp *Interpreter) initScopePkg(n *node) (*scope, string) {
|
func (interp *Interpreter) initScopePkg(pkgID string) *scope {
|
||||||
sc := interp.universe
|
sc := interp.universe
|
||||||
pkgName := mainID
|
|
||||||
|
|
||||||
if p := fileNode(n); p != nil {
|
|
||||||
pkgName = p.child[0].ident
|
|
||||||
}
|
|
||||||
|
|
||||||
interp.mutex.Lock()
|
interp.mutex.Lock()
|
||||||
if _, ok := interp.scopes[pkgName]; !ok {
|
if _, ok := interp.scopes[pkgID]; !ok {
|
||||||
interp.scopes[pkgName] = sc.pushBloc()
|
interp.scopes[pkgID] = sc.pushBloc()
|
||||||
}
|
}
|
||||||
sc = interp.scopes[pkgName]
|
sc = interp.scopes[pkgID]
|
||||||
|
sc.pkgID = pkgID
|
||||||
interp.mutex.Unlock()
|
interp.mutex.Unlock()
|
||||||
return sc, pkgName
|
return sc
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (interp *Interpreter) importSrc(rPath, path, alias string) error {
|
func (interp *Interpreter) importSrc(rPath, path string) error {
|
||||||
var dir string
|
var dir string
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -29,6 +29,7 @@ func (interp *Interpreter) importSrc(rPath, path, alias string) error {
|
|||||||
} else if dir, rPath, err = pkgDir(interp.context.GOPATH, rPath, path); err != nil {
|
} else if dir, rPath, err = pkgDir(interp.context.GOPATH, rPath, path); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if interp.rdir[path] {
|
if interp.rdir[path] {
|
||||||
return fmt.Errorf("import cycle not allowed\n\timports %s", path)
|
return fmt.Errorf("import cycle not allowed\n\timports %s", path)
|
||||||
}
|
}
|
||||||
@@ -66,6 +67,9 @@ func (interp *Interpreter) importSrc(rPath, path, alias string) error {
|
|||||||
if root == nil {
|
if root == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if interp.astDot {
|
||||||
|
root.astDot(dotX(), name)
|
||||||
|
}
|
||||||
if pkgName == "" {
|
if pkgName == "" {
|
||||||
pkgName = pname
|
pkgName = pname
|
||||||
} else if pkgName != pname {
|
} else if pkgName != pname {
|
||||||
@@ -75,7 +79,7 @@ func (interp *Interpreter) importSrc(rPath, path, alias string) error {
|
|||||||
|
|
||||||
subRPath := effectivePkg(rPath, path)
|
subRPath := effectivePkg(rPath, path)
|
||||||
var list []*node
|
var list []*node
|
||||||
list, err = interp.gta(root, subRPath)
|
list, err = interp.gta(root, subRPath, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -85,7 +89,7 @@ func (interp *Interpreter) importSrc(rPath, path, alias string) error {
|
|||||||
// revisit incomplete nodes where GTA could not complete
|
// revisit incomplete nodes where GTA could not complete
|
||||||
for pkg, nodes := range revisit {
|
for pkg, nodes := range revisit {
|
||||||
for _, n := range nodes {
|
for _, n := range nodes {
|
||||||
if _, err = interp.gta(n, pkg); err != nil {
|
if _, err = interp.gta(n, pkg, path); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,7 +98,7 @@ func (interp *Interpreter) importSrc(rPath, path, alias string) error {
|
|||||||
// Generate control flow graphs
|
// Generate control flow graphs
|
||||||
for _, root := range rootNodes {
|
for _, root := range rootNodes {
|
||||||
var nodes []*node
|
var nodes []*node
|
||||||
if nodes, err = interp.cfg(root); err != nil {
|
if nodes, err = interp.cfg(root, path); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
initNodes = append(initNodes, nodes...)
|
initNodes = append(initNodes, nodes...)
|
||||||
@@ -103,13 +107,7 @@ func (interp *Interpreter) importSrc(rPath, path, alias string) error {
|
|||||||
// Register source package in the interpreter. The package contains only
|
// Register source package in the interpreter. The package contains only
|
||||||
// the global symbols in the package scope.
|
// the global symbols in the package scope.
|
||||||
interp.mutex.Lock()
|
interp.mutex.Lock()
|
||||||
interp.srcPkg[path] = interp.scopes[pkgName].sym
|
interp.srcPkg[path] = interp.scopes[path].sym
|
||||||
|
|
||||||
// Rename imported pkgName to alias if they are different
|
|
||||||
if pkgName != alias {
|
|
||||||
interp.scopes[alias] = interp.scopes[pkgName]
|
|
||||||
delete(interp.scopes, pkgName)
|
|
||||||
}
|
|
||||||
|
|
||||||
interp.frame.mutex.Lock()
|
interp.frame.mutex.Lock()
|
||||||
interp.resizeFrame()
|
interp.resizeFrame()
|
||||||
@@ -189,7 +187,7 @@ func effectivePkg(root, path string) string {
|
|||||||
for i := 0; i < len(splitPath); i++ {
|
for i := 0; i < len(splitPath); i++ {
|
||||||
part := splitPath[len(splitPath)-1-i]
|
part := splitPath[len(splitPath)-1-i]
|
||||||
|
|
||||||
if part == splitRoot[len(splitRoot)-1-rootIndex] {
|
if part == splitRoot[len(splitRoot)-1-rootIndex] && i != 0 {
|
||||||
prevRootIndex = rootIndex
|
prevRootIndex = rootIndex
|
||||||
rootIndex++
|
rootIndex++
|
||||||
} else if prevRootIndex == rootIndex {
|
} else if prevRootIndex == rootIndex {
|
||||||
|
|||||||
@@ -103,22 +103,23 @@ type structField struct {
|
|||||||
|
|
||||||
// itype defines the internal representation of types in the interpreter
|
// itype defines the internal representation of types in the interpreter
|
||||||
type itype struct {
|
type itype struct {
|
||||||
cat tcat // Type category
|
cat tcat // Type category
|
||||||
field []structField // Array of struct fields if structT or interfaceT
|
field []structField // Array of struct fields if structT or interfaceT
|
||||||
key *itype // Type of key element if MapT or nil
|
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, mapT, ptrT, aliasT, arrayT or variadicT
|
||||||
arg []*itype // Argument types if funcT or nil
|
arg []*itype // Argument types if funcT or nil
|
||||||
ret []*itype // Return types if funcT or nil
|
ret []*itype // Return types if funcT or nil
|
||||||
method []*node // Associated methods or nil
|
method []*node // Associated methods or nil
|
||||||
name string // name of type within its package for a defined type
|
name string // name of type within its package for a defined type
|
||||||
path string // for a defined type, the package import path
|
path string // for a defined type, the package import path
|
||||||
size int // Size of array if ArrayT
|
size int // Size of array if ArrayT
|
||||||
rtype reflect.Type // Reflection type if ValueT, or nil
|
rtype reflect.Type // Reflection type if ValueT, or nil
|
||||||
incomplete bool // true if type must be parsed again (out of order declarations)
|
incomplete bool // true if type must be parsed again (out of order declarations)
|
||||||
untyped bool // true for a literal value (string or number)
|
untyped bool // true for a literal value (string or number)
|
||||||
sizedef bool // true if array size is computed from type definition
|
sizedef bool // true if array size is computed from type definition
|
||||||
node *node // root AST node of type definition
|
isBinMethod bool // true if the type refers to a bin method function
|
||||||
scope *scope // type declaration scope (in case of re-parse incomplete type)
|
node *node // root AST node of type definition
|
||||||
|
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
|
||||||
@@ -175,7 +176,7 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Evaluate constant array size expression
|
// Evaluate constant array size expression
|
||||||
if _, err = interp.cfg(n.child[0]); err != nil {
|
if _, err = interp.cfg(n.child[0], sc.pkgID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
t.incomplete = true
|
t.incomplete = true
|
||||||
@@ -482,7 +483,7 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
|
|||||||
if m, _ := lt.lookupMethod(name); m != nil {
|
if m, _ := lt.lookupMethod(name); m != nil {
|
||||||
t, err = nodeType(interp, sc, m.child[2])
|
t, err = nodeType(interp, sc, m.child[2])
|
||||||
} else if bm, _, _, ok := lt.lookupBinMethod(name); ok {
|
} else if bm, _, _, ok := lt.lookupBinMethod(name); ok {
|
||||||
t = &itype{cat: valueT, rtype: bm.Type}
|
t = &itype{cat: valueT, rtype: bm.Type, isBinMethod: true}
|
||||||
} else if ti := lt.lookupField(name); len(ti) > 0 {
|
} else if ti := lt.lookupField(name); len(ti) > 0 {
|
||||||
t = lt.fieldSeq(ti)
|
t = lt.fieldSeq(ti)
|
||||||
} else if bs, _, ok := lt.lookupBinField(name); ok {
|
} else if bs, _, ok := lt.lookupBinField(name); ok {
|
||||||
@@ -809,6 +810,22 @@ func (t *itype) lookupBinField(name string) (s reflect.StructField, index []int,
|
|||||||
return s, index, ok
|
return s, index, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// methodCallType returns a method function type without the receiver defined.
|
||||||
|
// The input type must be a method function type with the receiver as the first input argument.
|
||||||
|
func (t *itype) methodCallType() reflect.Type {
|
||||||
|
it := []reflect.Type{}
|
||||||
|
ni := t.rtype.NumIn()
|
||||||
|
for i := 1; i < ni; i++ {
|
||||||
|
it = append(it, t.rtype.In(i))
|
||||||
|
}
|
||||||
|
ot := []reflect.Type{}
|
||||||
|
no := t.rtype.NumOut()
|
||||||
|
for i := 0; i < no; i++ {
|
||||||
|
ot = append(ot, t.rtype.Out(i))
|
||||||
|
}
|
||||||
|
return reflect.FuncOf(it, ot, t.rtype.IsVariadic())
|
||||||
|
}
|
||||||
|
|
||||||
// getMethod returns a pointer to the method definition
|
// getMethod returns a pointer to the method definition
|
||||||
func (t *itype) getMethod(name string) *node {
|
func (t *itype) getMethod(name string) *node {
|
||||||
for _, m := range t.method {
|
for _, m := range t.method {
|
||||||
@@ -885,7 +902,9 @@ func (t *itype) refType(defined map[string]bool) reflect.Type {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if t.val != nil && defined[t.val.name] {
|
if t.val != nil && defined[t.val.name] && !t.val.incomplete && t.val.rtype == nil {
|
||||||
|
// Replace reference to self (direct or indirect) by an interface{} to handle
|
||||||
|
// recursive types with reflect.
|
||||||
t.val.rtype = interf
|
t.val.rtype = interf
|
||||||
}
|
}
|
||||||
switch t.cat {
|
switch t.cat {
|
||||||
@@ -914,7 +933,7 @@ func (t *itype) refType(defined map[string]bool) reflect.Type {
|
|||||||
case interfaceT:
|
case interfaceT:
|
||||||
t.rtype = interf
|
t.rtype = interf
|
||||||
case mapT:
|
case mapT:
|
||||||
t.rtype = reflect.MapOf(t.key.TypeOf(), t.val.TypeOf())
|
t.rtype = reflect.MapOf(t.key.refType(defined), t.val.refType(defined))
|
||||||
case ptrT:
|
case ptrT:
|
||||||
t.rtype = reflect.PtrTo(t.val.refType(defined))
|
t.rtype = reflect.PtrTo(t.val.refType(defined))
|
||||||
case structT:
|
case structT:
|
||||||
|
|||||||
@@ -180,11 +180,23 @@ func genValueInterface(n *node) func(*frame) reflect.Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func zeroInterfaceValue() reflect.Value {
|
||||||
|
n := &node{kind: basicLit, typ: &itype{cat: nilT, untyped: true}}
|
||||||
|
v := reflect.New(reflect.TypeOf((*interface{})(nil)).Elem()).Elem()
|
||||||
|
return reflect.ValueOf(valueInterface{n, v})
|
||||||
|
}
|
||||||
|
|
||||||
func genValueInterfaceValue(n *node) func(*frame) reflect.Value {
|
func genValueInterfaceValue(n *node) func(*frame) reflect.Value {
|
||||||
value := genValue(n)
|
value := genValue(n)
|
||||||
|
|
||||||
return func(f *frame) reflect.Value {
|
return func(f *frame) reflect.Value {
|
||||||
return value(f).Interface().(valueInterface).value
|
v := value(f)
|
||||||
|
if v.Interface().(valueInterface).node == nil {
|
||||||
|
// Uninitialized interface value, set it to a correct zero value.
|
||||||
|
v.Set(zeroInterfaceValue())
|
||||||
|
v = value(f)
|
||||||
|
}
|
||||||
|
return v.Interface().(valueInterface).value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// +build go1.12,!go1.14
|
// +build go1.12,!go1.14
|
||||||
|
|
||||||
|
// Package stdlib provides wrappers of standard library packages to be imported natively in Yaegi.
|
||||||
package stdlib
|
package stdlib
|
||||||
|
|
||||||
import "reflect"
|
import "reflect"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// +build go1.12,!go1.14
|
// +build go1.12,!go1.14
|
||||||
|
|
||||||
|
// Package syscall provide wrapper of standard library syscall package for native import in Yaegi.
|
||||||
package syscall
|
package syscall
|
||||||
|
|
||||||
import "reflect"
|
import "reflect"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// +build go1.12,!go1.14
|
// +build go1.12,!go1.14
|
||||||
|
|
||||||
|
// Package unsafe provides wrapper of standard library unsafe package to be imported natively in Yaegi.
|
||||||
package unsafe
|
package unsafe
|
||||||
|
|
||||||
import "reflect"
|
import "reflect"
|
||||||
|
|||||||
Reference in New Issue
Block a user