Compare commits

...

15 Commits

Author SHA1 Message Date
Marc Vertes
f1cde2be0f fix: apply automatic type conversion to constant expressions (#484) 2020-01-09 18:01:44 +01:00
Marc Vertes
bb04af2d4d doc: document import of source and binary packages (#477) 2020-01-08 18:51:30 +01:00
Marc Vertes
9a8a88dcb9 fix: use branch operation in || and && operators, fix storage for ! (#476) 2020-01-07 17:27:22 +01:00
Marc Vertes
f3f54a5302 doc: add explanation about CLA in CONTRIBUTING.md (#483) 2020-01-07 16:30:05 +01:00
Marc Vertes
878fcc835c fix: add support for ^ and + as unary operators 2020-01-07 15:34:05 +01:00
Marc Vertes
a1f2d3bf1d fix: isNil was not forwarding result when used in a branch expression 2019-12-19 18:32:04 +01:00
Marc Vertes
3cd37645eb fix: correct isValueUntyped() to handle typed constants 2019-12-19 15:38:05 +01:00
Marc Vertes
e1ac83f7d8 fix: correct type extraction for returned value 2019-12-17 10:18:06 +01:00
Marc Vertes
4f93be7f19 fix: emulate struct by interface{} only for recursive struct types 2019-12-16 19:00:07 +01:00
Marc Vertes
7a0c09f5eb fix: detect untyped values when importing from binary packages 2019-12-13 11:18:04 +01:00
Marc Vertes
275391c1e8 fix: struct type detection, collision between field and type name 2019-12-12 14:40:05 +01:00
Marc Vertes
273df8af9f fix: improve interface type checks using method sets 2019-12-11 14:46:06 +01:00
Marc Vertes
0d2c39d155 fix: implicit import package name was not correctly generated 2019-12-11 11:54:05 +01:00
Marc Vertes
1ff1a50753 fix: add method checks for interface types 2019-12-09 18:24:04 +01:00
Marc Vertes
488e491bf8 fix: improve type switch clause with assign 2019-11-27 23:00:04 +01:00
31 changed files with 645 additions and 69 deletions

View File

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

13
_test/bin2.go Normal file
View File

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

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

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

10
_test/import7.go Normal file
View File

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

17
_test/interface14.go Normal file
View File

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

28
_test/interface15.go Normal file
View File

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

25
_test/interface16.go Normal file
View File

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

17
_test/interface17.go Normal file
View File

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

18
_test/interface18.go Normal file
View File

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

15
_test/nil0.go Normal file
View File

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

12
_test/nil1.go Normal file
View File

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

9
_test/or0.go Normal file
View File

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

9
_test/or1.go Normal file
View File

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

11
_test/str4.go Normal file
View File

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

19
_test/struct29.go Normal file
View File

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

19
_test/struct30.go Normal file
View File

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

21
_test/switch22.go Normal file
View File

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

15
_test/time11.go Normal file
View File

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

View File

@@ -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)

View File

@@ -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)

View File

@@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"log" "log"
"math" "math"
"path"
"reflect" "reflect"
"regexp"
"unicode" "unicode"
) )
@@ -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){
@@ -32,6 +36,8 @@ var constBltn = map[string]func(*node){
"real": realConst, "real": realConst,
} }
var identifier = regexp.MustCompile(`([\pL_][\pL_\d]*)$`)
// cfg generates a control flow graph (CFG) from AST (wiring successors in AST) // cfg generates a control flow graph (CFG) from AST (wiring successors in AST)
// and pre-compute frame sizes and indexes for all un-named (temporary) and named // 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.
@@ -170,23 +176,21 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
var typ *itype var typ *itype
if len(n.child) == 2 { if len(n.child) == 2 {
// 1 type in clause: define the var with this type in the case clause scope // 1 type in clause: define the var with this type in the case clause scope
switch sym, _, ok := sc.lookup(n.child[0].ident); { switch {
case ok && sym.kind == typeSym:
typ = sym.typ
case n.child[0].kind == selectorExpr:
if typ, err = nodeType(interp, sc, n.child[0]); err != nil {
return false
}
case n.child[0].ident == "nil": case n.child[0].ident == "nil":
typ = sc.getType("interface{}") typ = sc.getType("interface{}")
default: case !n.child[0].isType(sc):
err = n.cfgErrorf("%s is not a type", n.child[0].ident) err = n.cfgErrorf("%s is not a type", n.child[0].ident)
return false default:
typ, err = nodeType(interp, sc, n.child[0])
} }
} else { } else {
// define the var with the type in the switch guard expression // define the var with the type in the switch guard expression
typ = sn.child[1].child[1].child[0].typ typ = sn.child[1].child[1].child[0].typ
} }
if err != nil {
return false
}
nod := n.lastChild().child[0] nod := n.lastChild().child[0]
index := sc.add(typ) index := sc.add(typ)
sc.sym[nod.ident] = &symbol{index: index, kind: varSym, typ: typ} sc.sym[nod.ident] = &symbol{index: index, kind: varSym, typ: typ}
@@ -318,7 +322,7 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
name = n.child[0].ident name = n.child[0].ident
} else { } else {
ipath = n.child[0].rval.String() ipath = n.child[0].rval.String()
name = path.Base(ipath) name = identifier.FindString(ipath)
} }
if interp.binPkg[ipath] != nil && name != "." { if interp.binPkg[ipath] != nil && name != "." {
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: binPkgT, path: ipath}} sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: binPkgT, path: ipath}}
@@ -549,7 +553,7 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf() t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf()
// Shift operator type is inherited from first parameter only // Shift operator type is inherited from first parameter only
// All other binary operators require both parameter types to be the same // All other binary operators require both parameter types to be the same
if !isShiftNode(n) && !c0.typ.untyped && !c1.typ.untyped && c0.typ.id() != c1.typ.id() { 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()) err = n.cfgErrorf("mismatched types %s and %s", c0.typ.id(), c1.typ.id())
break break
} }
@@ -602,8 +606,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
@@ -1021,6 +1023,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 +1034,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 +1076,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 {
@@ -1155,7 +1163,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
@@ -1377,16 +1385,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
@@ -1528,7 +1580,7 @@ func isBinType(v reflect.Value) bool { return v.IsValid() && v.Kind() == reflect
// isType returns true if node refers to a type definition, false otherwise // isType returns true if node refers to a type definition, false otherwise
func (n *node) isType(sc *scope) bool { func (n *node) isType(sc *scope) bool {
switch n.kind { switch n.kind {
case arrayType, chanType, funcType, mapType, structType, rtypeExpr: case arrayType, chanType, funcType, interfaceType, mapType, structType, rtypeExpr:
return true return true
case parenExpr, starExpr: case parenExpr, starExpr:
if len(n.child) == 1 { if len(n.child) == 1 {
@@ -1536,7 +1588,7 @@ func (n *node) isType(sc *scope) bool {
} }
case selectorExpr: case selectorExpr:
pkg, name := n.child[0].ident, n.child[1].ident pkg, name := n.child[0].ident, n.child[1].ident
if sym, _, ok := sc.lookup(pkg); ok { if sym, _, ok := sc.lookup(pkg); ok && sym.kind == pkgSym {
path := sym.typ.path path := sym.typ.path
if p, ok := n.interp.binPkg[path]; ok && isBinType(p[name]) { if p, ok := n.interp.binPkg[path]; ok && isBinType(p[name]) {
return true // Imported binary type return true // Imported binary type
@@ -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] }
@@ -1825,3 +1900,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()
}

View File

@@ -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

View File

@@ -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

View File

@@ -1,7 +1,6 @@
package interp package interp
import ( import (
"path"
"reflect" "reflect"
) )
@@ -31,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
} }
@@ -126,7 +126,7 @@ func (interp *Interpreter) gta(root *node, rpath string) ([]*node, error) {
name = n.child[0].ident name = n.child[0].ident
} else { } else {
ipath = n.child[0].rval.String() ipath = n.child[0].rval.String()
name = path.Base(ipath) name = identifier.FindString(ipath)
} }
// Try to import a binary package first, or a source package // Try to import a binary package first, or a source package
if interp.binPkg[ipath] != nil { if interp.binPkg[ipath] != nil {

View File

@@ -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"},
}) })
} }

View File

@@ -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)

View File

@@ -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
@@ -260,7 +262,7 @@ func assign(n *node) {
switch { switch {
case dest.typ.cat == interfaceT: case dest.typ.cat == interfaceT:
svalue[i] = genValueInterface(src) svalue[i] = genValueInterface(src)
case dest.typ.cat == valueT && dest.typ.rtype.Kind() == reflect.Interface: case (dest.typ.cat == valueT || dest.typ.cat == errorT) && dest.typ.rtype.Kind() == reflect.Interface:
svalue[i] = genInterfaceWrapper(src, dest.typ.rtype) svalue[i] = genInterfaceWrapper(src, dest.typ.rtype)
case dest.typ.cat == valueT && src.typ.cat == funcT: case dest.typ.cat == valueT && src.typ.cat == funcT:
svalue[i] = genFunctionWrapper(src) svalue[i] = genFunctionWrapper(src)
@@ -340,21 +342,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 +663,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()
@@ -843,7 +848,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 {
@@ -1246,7 +1251,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 +1275,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 +1982,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 +1992,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 +2041,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 +2258,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 +2459,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 +2487,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
} }
} }

View File

@@ -401,6 +401,9 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
t.method = m t.method = m
sym.typ = t sym.typ = t
} }
if t.node == nil {
t.node = n
}
} else { } else {
t.incomplete = true t.incomplete = true
sc.sym[n.ident] = &symbol{kind: typeSym, typ: t} sc.sym[n.ident] = &symbol{kind: typeSym, typ: t}
@@ -636,6 +639,74 @@ func (t *itype) finalize() (*itype, error) {
return t, err return t, err
} }
// Equals returns true if the given type is identical to the receiver one.
func (t *itype) equals(o *itype) bool {
switch ti, oi := isInterface(t), isInterface(o); {
case ti && oi:
return t.methods().equals(o.methods())
case ti && !oi:
return o.methods().contains(t.methods())
case oi && !ti:
return t.methods().contains(o.methods())
default:
return t.id() == o.id()
}
}
// MethodSet defines the set of methods signatures as strings, indexed per method name.
type methodSet map[string]string
// Contains returns true if the method set m contains the method set n.
func (m methodSet) contains(n methodSet) bool {
for k, v := range n {
if m[k] != v {
return false
}
}
return true
}
// Equal returns true if the method set m is equal to the method set n.
func (m methodSet) equals(n methodSet) bool {
return m.contains(n) && n.contains(m)
}
// Methods returns a map of method type strings, indexed by method names.
func (t *itype) methods() methodSet {
res := make(methodSet)
switch t.cat {
case interfaceT:
// Get methods from recursive analysis of interface fields
for _, f := range t.field {
if f.typ.cat == funcT {
res[f.name] = f.typ.TypeOf().String()
} else {
for k, v := range f.typ.methods() {
res[k] = v
}
}
}
case valueT, errorT:
// Get method from corresponding reflect.Type
for i := t.rtype.NumMethod() - 1; i >= 0; i-- {
m := t.rtype.Method(i)
res[m.Name] = m.Type.String()
}
case ptrT:
// Consider only methods where receiver is a pointer to type t
for _, m := range t.val.method {
if m.child[0].child[0].lastChild().typ.cat == ptrT {
res[m.ident] = m.typ.TypeOf().String()
}
}
default:
for _, m := range t.method {
res[m.ident] = m.typ.TypeOf().String()
}
}
return res
}
// id returns a unique type identificator string // id returns a unique type identificator string
func (t *itype) id() string { func (t *itype) id() string {
// TODO: if res is nil, build identity from String() // TODO: if res is nil, build identity from String()
@@ -814,7 +885,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 {
@@ -834,15 +907,9 @@ func (t *itype) refType(defined map[string]bool) reflect.Type {
in := make([]reflect.Type, len(t.arg)) in := make([]reflect.Type, len(t.arg))
out := make([]reflect.Type, len(t.ret)) out := make([]reflect.Type, len(t.ret))
for i, v := range t.arg { for i, v := range t.arg {
if defined[v.name] {
v.rtype = interf
}
in[i] = v.refType(defined) in[i] = v.refType(defined)
} }
for i, v := range t.ret { for i, v := range t.ret {
if defined[v.name] {
v.rtype = interf
}
out[i] = v.refType(defined) out[i] = v.refType(defined)
} }
t.rtype = reflect.FuncOf(in, out, false) t.rtype = reflect.FuncOf(in, out, false)
@@ -933,7 +1000,11 @@ func isInterface(t *itype) bool {
return isInterfaceSrc(t) || t.TypeOf().Kind() == reflect.Interface return isInterfaceSrc(t) || t.TypeOf().Kind() == reflect.Interface
} }
func isStruct(t *itype) bool { return t.TypeOf().Kind() == reflect.Struct } func isStruct(t *itype) bool {
// Test first for a struct category, because a recursive interpreter struct may be
// represented by an interface{} at reflect level.
return t.cat == structT || t.TypeOf().Kind() == reflect.Struct
}
func isBool(t *itype) bool { return t.TypeOf().Kind() == reflect.Bool } func isBool(t *itype) bool { return t.TypeOf().Kind() == reflect.Bool }

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"