Compare commits

...

55 Commits

Author SHA1 Message Date
Marc Vertes
b0cd93a936 fix: correct interrupt signal handling in REPL
Avoid goroutines leak, accumulation of defered functions and
spurious resets of signal handlers. Effectively catch interrupt
signal (Ctrl-C) to cancel current eval.

Fixes #713.
2020-08-12 22:22:03 +02:00
mpl
611a8c37fa interp: make REPL stricter about parsing errors
So far the REPL loop was treating any parsing error coming from
go/parser to generate the AST, as having occurred because the source
code was not yet complete (unfinished block). And it was therefore
ignoring all of them.

However, some of these errors are legitimate, and must be caught as soon
as they occur, otherwise the REPL cycle would stay in an errored state
forever (even when the block terminates), without the user getting any
feedback about it.

Therefore, this change adds an extra check when a parsing error occurs,
i.e. it verifies that it looks like an "EOF" error (unfinished block)
before it ignores it (as the user is supposed to terminate the block
eventually). Otherwise the error is treated just like a "non-parsing"
(cfg, gta, ...) error and printed out.

Fixes #637
2020-08-12 18:44:21 +02:00
Marc Vertes
e71ddc7edd chore: update golangci-lint config (#810)
Disable nlreturn. Fix one comment.
2020-08-12 17:46:28 +02:00
Ludovic Fernandez
1fe75f149d feat: update stdlib mapping for go1.15
- drop stdlib for go1.13
- generate stdlib for go1.15
-  update CI configuration.
2020-08-12 12:38:04 +02:00
Nicholas Wiersma
cdc352cee2 feat: add index and composite literal type checking
This adds type checking to both `IndexExpr` and `CompositeLitExpr` as well as handling any required constant type conversion.

This includes a change to the type propagation to the children of a composite literal. Previously in most cases the composite literal type was propagated to its children. This does not work with type checking as the actual child type is needed.
2020-08-11 15:58:04 +02:00
Nicholas Wiersma
88569f5df7 fix: interface call regression from #787
Fix #787 changes how interfaces are set on a struct (compositeSparce). This change however makes calling the interface panic. 

This PR reverts part of the change in #787 and adds a test to ensure it does not break again.
2020-08-10 16:32:05 +02:00
Marc Vertes
2ac0c6f70b feature: command line provide sub-commands
The Yaegi command line has been changed to provide subcommands.

The following sub-commands are provided:
- extract (formerly goexports)
- help
- run
- test

The previous behaviour is now implemented in run command which
is the default, so the change should be transparent.

In run command, prepare the ability to run a package or a directory
in addition to a file. Not implemented yet

The test command is not implemented yet.

The extract command is meant to generate wrappers to non stdlib
packages.

Fixes #639
2020-08-10 16:20:05 +02:00
Nicholas Wiersma
bd4ce37baa feat: refactor type checking
The previous type checking was off and did not do untyped type conversion. This endeavours to fix this with better type checking in its own type.
2020-07-31 14:00:03 +02:00
Nicholas Wiersma
9c4d3d1e5a chore: updated linter 2020-07-30 11:18:04 +02:00
Marc Vertes
25c681c1e6 fix: regression on range following #787 2020-07-30 10:52:05 +02:00
Marc Vertes
9c51f6bb69 fix: correct range on arrays of interface objects 2020-07-23 12:25:04 +02:00
Marc Vertes
589b2a0cd2 fix: correct conversion to int in slice index expressions. 2020-07-23 12:05:03 +02:00
Marc Vertes
68911f8b4e fix: type assertion expression was not forwarding type
The detection of special cases of interpreter functions and interfaces
is more precise. It allows at least to complete parsing of
code where type is derived from a type assertion expression.

Fixes #770.
2020-07-23 11:51:53 +02:00
mpl
e5a7b0de11 extract: new package to extract symbols from a dependency
cmd/goexports is now based on it.

Updates #639 

Co-authored-by: Marc Vertes <mvertes@free.fr>
2020-07-23 10:58:33 +02:00
mpl
563270ca02 interp: support yet another vendoring case
* interp: support another vendoring case

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

Fixes #758

* make linter happier

* address review comments

* fix, cleanup, add unit tests

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

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

* fix: do not wire global varDecl

* fix: wire and execute global vars

* chore: add tests

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

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

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

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

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

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

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

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

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

Fixes #486.

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

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

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

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

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

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

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

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

* fix: detect intermediate recursive structs

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

* fix: handle arb recursion

* chore: refactor dref to be consistent

* fix: invalid recursive struct issue

* fix: handle checkptr issue

* fix: move unsafe into function to stop ptr check

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

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

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

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

Fixes #727.

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

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

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

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

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

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

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

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

Fixes #435.

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

* chore: remove not needed travis env var.
2020-06-22 12:55:42 +02:00
Nicholas Wiersma
f19b7563ea feature: unsafe type conversion 2020-06-18 18:14:03 +02:00
Nicholas Wiersma
0643762852 fix: allow uint in make len and cap 2020-06-18 15:18:04 +02:00
mpl
7323d97023 interp: global scope redeclaration detection 2020-06-18 15:08:04 +02:00
498 changed files with 10990 additions and 7207 deletions

View File

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

View File

@@ -19,14 +19,13 @@ cache:
matrix:
fast_finish: true
include:
- go: 1.13.x
- go: 1.14.x
- go: 1.15.x
env: STABLE=true
env:
global:
- GO111MODULE=on
- CI=1
go_import_path: github.com/containous/yaegi

25
_test/addr0.go Normal file
View File

@@ -0,0 +1,25 @@
package main
import (
"fmt"
"net/http"
)
type extendedRequest struct {
http.Request
Data string
}
func main() {
r := extendedRequest{}
req := &r.Request
fmt.Println(r)
fmt.Println(req)
}
// Output:
// {{ <nil> 0 0 map[] <nil> <nil> 0 [] false map[] map[] <nil> map[] <nil> <nil> <nil> <nil>} }
// &{ <nil> 0 0 map[] <nil> <nil> 0 [] false map[] map[] <nil> map[] <nil> <nil> <nil> <nil>}

19
_test/assign13.go Normal file
View File

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

16
_test/assign14.go Normal file
View File

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

11
_test/assign15.go Normal file
View File

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

12
_test/chan10.go Normal file
View File

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

14
_test/comp2.go Normal file
View File

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

15
_test/composite11.go Normal file
View File

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

13
_test/const14.go Normal file
View File

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

17
_test/const15.go Normal file
View File

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

32
_test/defer5.go Normal file
View File

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

27
_test/defer6.go Normal file
View File

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

18
_test/defer7.go Normal file
View File

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

24
_test/defer8.go Normal file
View File

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

21
_test/defer9.go Normal file
View File

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

12
_test/init1.go Normal file
View File

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

19
_test/interface44.go Normal file
View File

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

13
_test/interface45.go Normal file
View File

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

35
_test/interface46.go Normal file
View File

@@ -0,0 +1,35 @@
package main
import "fmt"
type I interface {
Foo() string
}
type Printer struct {
i I
}
func New(i I) *Printer {
return &Printer{
i: i,
}
}
func (p *Printer) Print() {
fmt.Println(p.i.Foo())
}
type T struct{}
func (t *T) Foo() string {
return "test"
}
func main() {
g := New(&T{})
g.Print()
}
// Output:
// test

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

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

25
_test/issue-770.go Normal file
View File

@@ -0,0 +1,25 @@
package main
import "reflect"
type I interface {
Foo() int
}
type T struct {
Name string
}
func (t T) Foo() int { return 0 }
func f(v reflect.Value) int {
i := v.Interface().(I)
return i.Foo()
}
func main() {
println("hello")
}
// Output:
// hello

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

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

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

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

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

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

14
_test/issue-782.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
func main() {
a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
from := uint32(2)
to := uint32(4)
b := a[from:to]
fmt.Print(b)
}
// Output:
// [3 4]

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

@@ -0,0 +1,39 @@
package main
import "fmt"
// Filter is a filter interface
type Filter interface {
Bounds(srcBounds string) (dstBounds string)
}
// GIFT type
type GIFT struct {
Filters []Filter
}
// New creates a new filter list and initializes it with the given slice of filters.
func New(filters ...Filter) *GIFT {
return &GIFT{
Filters: filters,
}
}
// Bounds calculates the appropriate bounds for the result image after applying all the added filters.
func (g *GIFT) Bounds(srcBounds string) (dstBounds string) {
dstBounds = srcBounds
for _, f := range g.Filters {
dstBounds = f.Bounds(dstBounds)
}
return dstBounds
}
func main() {
var filters []Filter
bounds := "foo"
g := New(filters...)
fmt.Println(g.Bounds(bounds))
}
// Output:
// foo

12
_test/make2.go Normal file
View File

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

12
_test/math2.go Normal file
View File

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

32
_test/math3.go Normal file
View File

@@ -0,0 +1,32 @@
package main
import (
"crypto/md5"
"fmt"
)
func md5Crypt(password, salt, magic []byte) []byte {
d := md5.New()
d.Write(password)
d.Write(magic)
d.Write(salt)
d2 := md5.New()
d2.Write(password)
d2.Write(salt)
for i, mixin := 0, d2.Sum(nil); i < len(password); i++ {
d.Write([]byte{mixin[i%16]})
}
return d.Sum(nil)
}
func main() {
b := md5Crypt([]byte("1"), []byte("2"), []byte("3"))
fmt.Println(b)
}
// Output:
// [187 141 73 89 101 229 33 106 226 63 117 234 117 149 230 21]

View File

@@ -7,4 +7,4 @@ func main() {
}
// Error:
// 5:2: illegal operand types for '+=' operator
// 5:2: invalid operation: mismatched types int and float64

17
_test/op6.go Normal file
View File

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

17
_test/op7.go Normal file
View File

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

21
_test/op8.go Normal file
View File

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

11
_test/op9.go Normal file
View File

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

11
_test/range9.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

18
_test/restricted0.go Normal file
View File

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

18
_test/restricted1.go Normal file
View File

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

14
_test/restricted2.go Normal file
View File

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

23
_test/restricted3.go Normal file
View File

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

12
_test/rune1.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import "fmt"
func main() {
t := make([]byte, 2)
t[0] = '$'
fmt.Println(t)
}
// Output:
// [36 0]

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

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

View File

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

33
_test/struct49.go Normal file
View File

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

20
_test/struct50.go Normal file
View File

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

23
_test/struct51.go Normal file
View File

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

20
_test/struct52.go Normal file
View File

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

23
_test/struct53.go Normal file
View File

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

26
_test/struct54.go Normal file
View File

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

28
_test/switch38.go Normal file
View File

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

View File

@@ -1,5 +1,5 @@
// The following comment line has the same effect as 'go run -tags=dummy'
//yaegi:tags dummy
// yaegi:tags dummy
package main

18
_test/time13.go Normal file
View File

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

44
_test/type25.go Normal file
View File

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

44
_test/type26.go Normal file
View File

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

15
_test/unsafe0.go Normal file
View File

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

20
_test/unsafe1.go Normal file
View File

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

20
_test/unsafe2.go Normal file
View File

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

26
_test/unsafe3.go Normal file
View File

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

29
_test/unsafe4.go Normal file
View File

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

22
_test/unsafe5.go Normal file
View File

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

13
_test/var12.go Normal file
View File

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

23
_test/var13.go Normal file
View File

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

10
_test/var14.go Normal file
View File

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

17
_test/variadic8.go Normal file
View File

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

14
_test/variadic9.go Normal file
View File

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

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

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

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

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

View File

@@ -23,268 +23,16 @@ import (
"bytes"
"flag"
"fmt"
"go/constant"
"go/format"
"go/importer"
"go/token"
"go/types"
"io/ioutil"
"io"
"log"
"math/big"
"os"
"path"
"runtime"
"strconv"
"strings"
"text/template"
"github.com/containous/yaegi/extract"
)
const model = `// Code generated by 'goexports {{.PkgName}}'. DO NOT EDIT.
{{.License}}
{{if .BuildTags}}// +build {{.BuildTags}}{{end}}
package {{.Dest}}
import (
{{- range $key, $value := .Imports }}
{{- if $value}}
"{{$key}}"
{{- end}}
{{- end}}
"{{.PkgName}}"
"reflect"
)
func init() {
Symbols["{{.PkgName}}"] = map[string]reflect.Value{
{{- if .Val}}
// function, constant and variable definitions
{{range $key, $value := .Val -}}
{{- if $value.Addr -}}
"{{$key}}": reflect.ValueOf(&{{$value.Name}}).Elem(),
{{else -}}
"{{$key}}": reflect.ValueOf({{$value.Name}}),
{{end -}}
{{end}}
{{- end}}
{{- if .Typ}}
// type definitions
{{range $key, $value := .Typ -}}
"{{$key}}": reflect.ValueOf((*{{$value}})(nil)),
{{end}}
{{- end}}
{{- if .Wrap}}
// interface wrapper definitions
{{range $key, $value := .Wrap -}}
"_{{$key}}": reflect.ValueOf((*{{$value.Name}})(nil)),
{{end}}
{{- end}}
}
}
{{range $key, $value := .Wrap -}}
// {{$value.Name}} is an interface wrapper for {{$key}} type
type {{$value.Name}} struct {
{{range $m := $value.Method -}}
W{{$m.Name}} func{{$m.Param}} {{$m.Result}}
{{end}}
}
{{range $m := $value.Method -}}
func (W {{$value.Name}}) {{$m.Name}}{{$m.Param}} {{$m.Result}} { {{$m.Ret}} W.W{{$m.Name}}{{$m.Arg}} }
{{end}}
{{end}}
`
// Val store the value name and addressable status of symbols
type Val struct {
Name string // "package.name"
Addr bool // true if symbol is a Var
}
// Method store information for generating interface wrapper method
type Method struct {
Name, Param, Result, Arg, Ret string
}
// Wrap store information for generating interface wrapper
type Wrap struct {
Name string
Method []Method
}
func genContent(dest, pkgName, license string) ([]byte, error) {
p, err := importer.ForCompiler(token.NewFileSet(), "source", nil).Import(pkgName)
if err != nil {
return nil, err
}
prefix := "_" + pkgName + "_"
prefix = strings.NewReplacer("/", "_", "-", "_", ".", "_").Replace(prefix)
typ := map[string]string{}
val := map[string]Val{}
wrap := map[string]Wrap{}
imports := map[string]bool{}
sc := p.Scope()
for _, pkg := range p.Imports() {
imports[pkg.Path()] = false
}
qualify := func(pkg *types.Package) string {
if pkg.Path() != pkgName {
imports[pkg.Path()] = true
}
return pkg.Name()
}
for _, name := range sc.Names() {
o := sc.Lookup(name)
if !o.Exported() {
continue
}
pname := path.Base(pkgName) + "." + name
switch o := o.(type) {
case *types.Const:
if b, ok := o.Type().(*types.Basic); ok && (b.Info()&types.IsUntyped) != 0 {
// convert untyped constant to right type to avoid overflow
val[name] = Val{fixConst(pname, o.Val(), imports), false}
} else {
val[name] = Val{pname, false}
}
case *types.Func:
val[name] = Val{pname, false}
case *types.Var:
val[name] = Val{pname, true}
case *types.TypeName:
typ[name] = pname
if t, ok := o.Type().Underlying().(*types.Interface); ok {
var methods []Method
for i := 0; i < t.NumMethods(); i++ {
f := t.Method(i)
if !f.Exported() {
continue
}
sign := f.Type().(*types.Signature)
args := make([]string, sign.Params().Len())
params := make([]string, len(args))
for j := range args {
v := sign.Params().At(j)
if args[j] = v.Name(); args[j] == "" {
args[j] = fmt.Sprintf("a%d", j)
}
params[j] = args[j] + " " + types.TypeString(v.Type(), qualify)
}
arg := "(" + strings.Join(args, ", ") + ")"
param := "(" + strings.Join(params, ", ") + ")"
results := make([]string, sign.Results().Len())
for j := range results {
v := sign.Results().At(j)
results[j] = v.Name() + " " + types.TypeString(v.Type(), qualify)
}
result := "(" + strings.Join(results, ", ") + ")"
ret := ""
if sign.Results().Len() > 0 {
ret = "return"
}
methods = append(methods, Method{f.Name(), param, result, arg, ret})
}
wrap[name] = Wrap{prefix + name, methods}
}
}
}
var buildTags string
if runtime.Version() != "devel" {
parts := strings.Split(runtime.Version(), ".")
minorRaw := getMinor(parts[1])
currentGoVersion := parts[0] + "." + minorRaw
minor, errParse := strconv.Atoi(minorRaw)
if errParse != nil {
return nil, fmt.Errorf("failed to parse version: %v", errParse)
}
nextGoVersion := parts[0] + "." + strconv.Itoa(minor+1)
buildTags = currentGoVersion + ",!" + nextGoVersion
}
base := template.New("goexports")
parse, err := base.Parse(model)
if err != nil {
return nil, fmt.Errorf("template parsing error: %v", err)
}
if pkgName == "log/syslog" {
buildTags += ",!windows,!nacl,!plan9"
}
b := new(bytes.Buffer)
data := map[string]interface{}{
"Dest": dest,
"Imports": imports,
"PkgName": pkgName,
"Val": val,
"Typ": typ,
"Wrap": wrap,
"BuildTags": buildTags,
"License": license,
}
err = parse.Execute(b, data)
if err != nil {
return nil, fmt.Errorf("template error: %v", err)
}
// gofmt
source, err := format.Source(b.Bytes())
if err != nil {
return nil, fmt.Errorf("failed to format source: %v: %s", err, b.Bytes())
}
return source, nil
}
// fixConst checks untyped constant value, converting it if necessary to avoid overflow
func fixConst(name string, val constant.Value, imports map[string]bool) string {
var (
tok string
str string
)
switch val.Kind() {
case constant.Int:
tok = "INT"
str = val.ExactString()
case constant.Float:
v := constant.Val(val) // v is *big.Rat or *big.Float
f, ok := v.(*big.Float)
if !ok {
f = new(big.Float).SetRat(v.(*big.Rat))
}
tok = "FLOAT"
str = f.Text('g', int(f.Prec()))
case constant.Complex:
// TODO: not sure how to parse this case
fallthrough
default:
return name
}
imports["go/constant"] = true
imports["go/token"] = true
return fmt.Sprintf("constant.MakeFromLiteral(\"%s\", token.%s, 0)", str, tok)
}
// genLicense generates the correct LICENSE header text from the provided
// path to a LICENSE file.
func genLicense(fname string) (string, error) {
@@ -314,9 +62,13 @@ func genLicense(fname string) (string, error) {
return license.String(), nil
}
func main() {
licenseFlag := flag.String("license", "", "path to a LICENSE file")
var (
licenseFlag = flag.String("license", "", "path to a LICENSE file")
// TODO: deal with a module that has several packages (so there's only one go.mod file at the root of the project).
importPathFlag = flag.String("import_path", "", "the namespace for the symbols extracted from the argument. Not needed if the argument is from the stdlib, or if the name can be found in a go.mod")
)
func main() {
flag.Parse()
if flag.NArg() == 0 {
@@ -329,50 +81,57 @@ func main() {
log.Fatal(err)
}
dir, err := os.Getwd()
wd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
dest := path.Base(dir)
for _, pkg := range flag.Args() {
content, err := genContent(dest, pkg, license)
ext := extract.Extractor{
Dest: path.Base(wd),
License: license,
}
goos, goarch := os.Getenv("GOOS"), os.Getenv("GOARCH")
skip := map[string]bool{}
if goos == "solaris" {
skip["syscall.RawSyscall6"] = true
skip["syscall.Syscall6"] = true
}
ext.Skip = skip
for _, pkgIdent := range flag.Args() {
var buf bytes.Buffer
importPath, err := ext.Extract(pkgIdent, *importPathFlag, &buf)
if err != nil {
log.Println(err)
continue
}
var oFile string
if pkg == "syscall" {
goos, arch := os.Getenv("GOOS"), os.Getenv("GOARCH")
oFile = strings.Replace(pkg, "/", "_", -1) + "_" + goos + "_" + arch + ".go"
if pkgIdent == "syscall" {
oFile = strings.Replace(importPath, "/", "_", -1) + "_" + goos + "_" + goarch + ".go"
} else {
oFile = strings.Replace(pkg, "/", "_", -1) + ".go"
oFile = strings.Replace(importPath, "/", "_", -1) + ".go"
}
prefix := runtime.Version()
if runtime.Version() != "devel" {
parts := strings.Split(runtime.Version(), ".")
prefix = parts[0] + "_" + getMinor(parts[1])
prefix = parts[0] + "_" + extract.GetMinor(parts[1])
}
err = ioutil.WriteFile(prefix+"_"+oFile, content, 0666)
f, err := os.Create(prefix + "_" + oFile)
if err != nil {
log.Fatal(err)
}
if _, err := io.Copy(f, &buf); err != nil {
_ = f.Close()
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
}
}
func getMinor(part string) string {
minor := part
index := strings.Index(minor, "beta")
if index < 0 {
index = strings.Index(minor, "rc")
}
if index > 0 {
minor = minor[:index]
}
return minor
}

107
cmd/yaegi/extract.go Normal file
View File

@@ -0,0 +1,107 @@
package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"io"
"os"
"path"
"strings"
"github.com/containous/yaegi/extract"
)
func extractCmd(arg []string) error {
var licensePath string
var importPath string
eflag := flag.NewFlagSet("run", flag.ContinueOnError)
eflag.StringVar(&licensePath, "license", "", "path to a LICENSE file")
eflag.StringVar(&importPath, "import_path", "", "the namespace for the extracted symbols")
eflag.Usage = func() {
fmt.Println("Usage: yaegi extract [options] packages...")
fmt.Println("Options:")
eflag.PrintDefaults()
}
if err := eflag.Parse(arg); err != nil {
return err
}
args := eflag.Args()
if len(args) == 0 {
return fmt.Errorf("missing package")
}
license, err := genLicense(licensePath)
if err != nil {
return err
}
wd, err := os.Getwd()
if err != nil {
return err
}
ext := extract.Extractor{
Dest: path.Base(wd),
License: license,
}
for _, pkgIdent := range args {
var buf bytes.Buffer
importPath, err := ext.Extract(pkgIdent, importPath, &buf)
if err != nil {
fmt.Fprintln(os.Stderr, err)
continue
}
oFile := strings.Replace(importPath, "/", "_", -1) + ".go"
f, err := os.Create(oFile)
if err != nil {
return err
}
if _, err := io.Copy(f, &buf); err != nil {
_ = f.Close()
return err
}
if err := f.Close(); err != nil {
return err
}
}
return nil
}
// genLicense generates the correct LICENSE header text from the provided
// path to a LICENSE file.
func genLicense(fname string) (string, error) {
if fname == "" {
return "", nil
}
f, err := os.Open(fname)
if err != nil {
return "", fmt.Errorf("could not open LICENSE file: %v", err)
}
defer func() { _ = f.Close() }()
license := new(strings.Builder)
sc := bufio.NewScanner(f)
for sc.Scan() {
txt := sc.Text()
if txt != "" {
txt = " " + txt
}
license.WriteString("//" + txt + "\n")
}
if sc.Err() != nil {
return "", fmt.Errorf("could not scan LICENSE file: %v", err)
}
return license.String(), nil
}

43
cmd/yaegi/help.go Normal file
View File

@@ -0,0 +1,43 @@
package main
import "fmt"
const usage = `Yaegi is a Go interpreter.
Usage:
yaegi [command] [arguments]
The commands are:
extract generate a wrapper file from a source package
help print usage information
run execute a Go program from source
test execute test functions in a Go package
Use "yaegi help <command>" for more information about a command.
If no command is given or if the first argument is not a command, then
the run command is assumed.
`
func help(arg []string) error {
var cmd string
if len(arg) > 0 {
cmd = arg[0]
}
switch cmd {
case Extract:
return extractCmd([]string{"-h"})
case Help, "", "-h", "--help":
fmt.Print(usage)
return nil
case Run:
return run([]string{"-h"})
case Test:
return fmt.Errorf("help: test not implemented")
default:
return fmt.Errorf("help: invalid yaegi command: %v", cmd)
}
}

132
cmd/yaegi/run.go Normal file
View File

@@ -0,0 +1,132 @@
package main
import (
"flag"
"fmt"
"go/build"
"io/ioutil"
"os"
"strings"
"github.com/containous/yaegi/interp"
"github.com/containous/yaegi/stdlib"
"github.com/containous/yaegi/stdlib/syscall"
"github.com/containous/yaegi/stdlib/unrestricted"
"github.com/containous/yaegi/stdlib/unsafe"
)
func run(arg []string) error {
var interactive bool
var useSyscall bool
var useUnrestricted bool
var useUnsafe bool
var tags string
var cmd string
var err error
rflag := flag.NewFlagSet("run", flag.ContinueOnError)
rflag.BoolVar(&interactive, "i", false, "start an interactive REPL")
rflag.BoolVar(&useSyscall, "syscall", false, "include syscall symbols")
rflag.BoolVar(&useUnrestricted, "unrestricted", false, "include unrestricted symbols")
rflag.StringVar(&tags, "tags", "", "set a list of build tags")
rflag.BoolVar(&useUnsafe, "unsafe", false, "include usafe symbols")
rflag.StringVar(&cmd, "e", "", "set the command to be executed (instead of script or/and shell)")
rflag.Usage = func() {
fmt.Println("Usage: yaegi run [options] [path] [args]")
fmt.Println("Options:")
rflag.PrintDefaults()
}
if err = rflag.Parse(arg); err != nil {
return err
}
args := rflag.Args()
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
i.Use(stdlib.Symbols)
i.Use(interp.Symbols)
if useSyscall {
i.Use(syscall.Symbols)
}
if useUnsafe {
i.Use(unsafe.Symbols)
}
if useUnrestricted {
// Use of unrestricted symbols should always follow use of stdlib symbols, to update them.
i.Use(unrestricted.Symbols)
}
if cmd != "" {
i.REPL(strings.NewReader(cmd), os.Stderr)
}
if len(args) == 0 {
if interactive || cmd == "" {
i.REPL(os.Stdin, os.Stdout)
}
return nil
}
// Skip first os arg to set command line as expected by interpreted main
path := args[0]
os.Args = arg[1:]
flag.CommandLine = flag.NewFlagSet(path, flag.ExitOnError)
if isPackageName(path) {
err = runPackage(i, path)
} else {
if isDir(path) {
err = runDir(i, path)
} else {
err = runFile(i, path)
}
}
if err != nil {
return err
}
if interactive {
i.REPL(os.Stdin, os.Stdout)
}
return nil
}
func isPackageName(path string) bool {
return !strings.HasPrefix(path, "/") && !strings.HasPrefix(path, "./") && !strings.HasPrefix(path, "../")
}
func isDir(path string) bool {
fi, err := os.Lstat(path)
return err == nil && fi.IsDir()
}
func runPackage(i *interp.Interpreter, path string) error {
return fmt.Errorf("runPackage not implemented")
}
func runDir(i *interp.Interpreter, path string) error {
return fmt.Errorf("runDir not implemented")
}
func runFile(i *interp.Interpreter, path string) error {
b, err := ioutil.ReadFile(path)
if err != nil {
return err
}
if s := string(b); strings.HasPrefix(s, "#!") {
// Allow executable go scripts, Have the same behavior as in interactive mode.
s = strings.Replace(s, "#!", "//", 1)
i.REPL(strings.NewReader(s), os.Stdout)
} else {
// Files not starting with "#!" are supposed to be pure Go, directly Evaled.
i.Name = path
_, err := i.Eval(s)
if err != nil {
fmt.Println(err)
if p, ok := err.(interp.Panic); ok {
fmt.Println(string(p.Stack))
}
}
}
return nil
}

View File

@@ -63,9 +63,13 @@ Options:
evaluate the string and return.
-i
start an interactive REPL after file execution.
-syscall
include syscall symbols.
-tags tag,list
a comma-separated list of build tags to consider satisfied during
the interpretation.
-unsafe
include unsafe symbols.
Debugging support (may be removed at any time):
YAEGI_AST_DOT=1
@@ -81,75 +85,53 @@ Debugging support (may be removed at any time):
package main
import (
"errors"
"flag"
"fmt"
"go/build"
"io/ioutil"
"log"
"os"
"strings"
)
"github.com/containous/yaegi/interp"
"github.com/containous/yaegi/stdlib"
const (
Extract = "extract"
Help = "help"
Run = "run"
Test = "test"
)
func main() {
var interactive bool
var tags string
var cmd string
flag.BoolVar(&interactive, "i", false, "start an interactive REPL")
flag.StringVar(&tags, "tags", "", "set a list of build tags")
flag.StringVar(&cmd, "e", "", "set the command to be executed (instead of script or/and shell)")
flag.Usage = func() {
fmt.Println("Usage:", os.Args[0], "[options] [script] [args]")
fmt.Println("Options:")
flag.PrintDefaults()
}
flag.Parse()
args := flag.Args()
log.SetFlags(log.Lshortfile)
var err error
var exitCode int
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
i.Use(stdlib.Symbols)
i.Use(interp.Symbols)
log.SetFlags(log.Lshortfile) // Ease debugging.
if cmd != `` {
i.REPL(strings.NewReader(cmd), os.Stderr)
if len(os.Args) > 1 {
cmd = os.Args[1]
}
if len(args) == 0 {
if interactive || cmd == `` {
i.REPL(os.Stdin, os.Stdout)
}
return
switch cmd {
case Extract:
err = extractCmd(os.Args[2:])
case Help, "-h", "--help":
err = help(os.Args[2:])
case Run:
err = run(os.Args[2:])
case Test:
err = fmt.Errorf("test not implemented")
default:
// If no command is given, fallback to default "run" command.
// This allows scripts starting with "#!/usr/bin/env yaegi",
// as passing more than 1 argument to #! executable may be not supported
// on all platforms.
cmd = Run
err = run(os.Args[1:])
}
// Skip first os arg to set command line as expected by interpreted main
os.Args = os.Args[1:]
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
b, err := ioutil.ReadFile(args[0])
if err != nil {
log.Fatal("Could not read file: ", args[0])
}
if s := string(b); strings.HasPrefix(s, "#!") {
// Allow executable go scripts, Have the same behavior as in interactive mode.
s = strings.Replace(s, "#!", "//", 1)
i.REPL(strings.NewReader(s), os.Stdout)
} else {
// Files not starting with "#!" are supposed to be pure Go, directly Evaled.
i.Name = args[0]
_, err := i.Eval(s)
if err != nil {
fmt.Println(err)
if p, ok := err.(interp.Panic); ok {
fmt.Println(string(p.Stack))
}
}
}
if interactive {
i.REPL(os.Stdin, os.Stdout)
if err != nil && !errors.Is(err, flag.ErrHelp) {
err = fmt.Errorf("%s: %w", cmd, err)
fmt.Fprintln(os.Stderr, err)
exitCode = 1
}
os.Exit(exitCode)
}

View File

@@ -12,7 +12,7 @@ import (
)
const (
// CITimeoutMultiplier is the multiplier for all timeouts in the CI
// CITimeoutMultiplier is the multiplier for all timeouts in the CI.
CITimeoutMultiplier = 3
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

409
extract/extract.go Normal file
View File

@@ -0,0 +1,409 @@
/*
Package extract generates wrappers of package exported symbols.
*/
package extract
import (
"bufio"
"bytes"
"errors"
"fmt"
"go/constant"
"go/format"
"go/importer"
"go/token"
"go/types"
"io"
"math/big"
"os"
"path"
"path/filepath"
"runtime"
"strconv"
"strings"
"text/template"
)
const model = `// Code generated by 'github.com/containous/yaegi/extract {{.PkgName}}'. DO NOT EDIT.
{{.License}}
{{if .BuildTags}}// +build {{.BuildTags}}{{end}}
package {{.Dest}}
import (
{{- range $key, $value := .Imports }}
{{- if $value}}
"{{$key}}"
{{- end}}
{{- end}}
"{{.PkgName}}"
"reflect"
)
func init() {
Symbols["{{.PkgName}}"] = map[string]reflect.Value{
{{- if .Val}}
// function, constant and variable definitions
{{range $key, $value := .Val -}}
{{- if $value.Addr -}}
"{{$key}}": reflect.ValueOf(&{{$value.Name}}).Elem(),
{{else -}}
"{{$key}}": reflect.ValueOf({{$value.Name}}),
{{end -}}
{{end}}
{{- end}}
{{- if .Typ}}
// type definitions
{{range $key, $value := .Typ -}}
"{{$key}}": reflect.ValueOf((*{{$value}})(nil)),
{{end}}
{{- end}}
{{- if .Wrap}}
// interface wrapper definitions
{{range $key, $value := .Wrap -}}
"_{{$key}}": reflect.ValueOf((*{{$value.Name}})(nil)),
{{end}}
{{- end}}
}
}
{{range $key, $value := .Wrap -}}
// {{$value.Name}} is an interface wrapper for {{$key}} type
type {{$value.Name}} struct {
{{range $m := $value.Method -}}
W{{$m.Name}} func{{$m.Param}} {{$m.Result}}
{{end}}
}
{{range $m := $value.Method -}}
func (W {{$value.Name}}) {{$m.Name}}{{$m.Param}} {{$m.Result}} { {{$m.Ret}} W.W{{$m.Name}}{{$m.Arg}} }
{{end}}
{{end}}
`
// Val stores the value name and addressable status of symbols.
type Val struct {
Name string // "package.name"
Addr bool // true if symbol is a Var
}
// Method stores information for generating interface wrapper method.
type Method struct {
Name, Param, Result, Arg, Ret string
}
// Wrap stores information for generating interface wrapper.
type Wrap struct {
Name string
Method []Method
}
// restricted map defines symbols for which a special implementation is provided.
var restricted = map[string]bool{
"osExit": true,
"osFindProcess": true,
"logFatal": true,
"logFatalf": true,
"logFatalln": true,
"logLogger": true,
"logNew": true,
}
func genContent(dest, importPath, license string, p *types.Package, skip map[string]bool) ([]byte, error) {
prefix := "_" + importPath + "_"
prefix = strings.NewReplacer("/", "_", "-", "_", ".", "_").Replace(prefix)
typ := map[string]string{}
val := map[string]Val{}
wrap := map[string]Wrap{}
imports := map[string]bool{}
sc := p.Scope()
for _, pkg := range p.Imports() {
imports[pkg.Path()] = false
}
qualify := func(pkg *types.Package) string {
if pkg.Path() != importPath {
imports[pkg.Path()] = true
}
return pkg.Name()
}
for _, name := range sc.Names() {
o := sc.Lookup(name)
if !o.Exported() {
continue
}
pname := path.Base(importPath) + "." + name
if skip[pname] {
continue
}
if rname := path.Base(importPath) + name; restricted[rname] {
// Restricted symbol, locally provided by stdlib wrapper.
pname = rname
}
switch o := o.(type) {
case *types.Const:
if b, ok := o.Type().(*types.Basic); ok && (b.Info()&types.IsUntyped) != 0 {
// convert untyped constant to right type to avoid overflow
val[name] = Val{fixConst(pname, o.Val(), imports), false}
} else {
val[name] = Val{pname, false}
}
case *types.Func:
val[name] = Val{pname, false}
case *types.Var:
val[name] = Val{pname, true}
case *types.TypeName:
typ[name] = pname
if t, ok := o.Type().Underlying().(*types.Interface); ok {
var methods []Method
for i := 0; i < t.NumMethods(); i++ {
f := t.Method(i)
if !f.Exported() {
continue
}
sign := f.Type().(*types.Signature)
args := make([]string, sign.Params().Len())
params := make([]string, len(args))
for j := range args {
v := sign.Params().At(j)
if args[j] = v.Name(); args[j] == "" {
args[j] = fmt.Sprintf("a%d", j)
}
params[j] = args[j] + " " + types.TypeString(v.Type(), qualify)
}
arg := "(" + strings.Join(args, ", ") + ")"
param := "(" + strings.Join(params, ", ") + ")"
results := make([]string, sign.Results().Len())
for j := range results {
v := sign.Results().At(j)
results[j] = v.Name() + " " + types.TypeString(v.Type(), qualify)
}
result := "(" + strings.Join(results, ", ") + ")"
ret := ""
if sign.Results().Len() > 0 {
ret = "return"
}
methods = append(methods, Method{f.Name(), param, result, arg, ret})
}
wrap[name] = Wrap{prefix + name, methods}
}
}
}
buildTags, err := buildTags()
if err != nil {
return nil, err
}
base := template.New("goexports")
parse, err := base.Parse(model)
if err != nil {
return nil, fmt.Errorf("template parsing error: %v", err)
}
if importPath == "log/syslog" {
buildTags += ",!windows,!nacl,!plan9"
}
b := new(bytes.Buffer)
data := map[string]interface{}{
"Dest": dest,
"Imports": imports,
"PkgName": importPath,
"Val": val,
"Typ": typ,
"Wrap": wrap,
"BuildTags": buildTags,
"License": license,
}
err = parse.Execute(b, data)
if err != nil {
return nil, fmt.Errorf("template error: %v", err)
}
// gofmt
source, err := format.Source(b.Bytes())
if err != nil {
return nil, fmt.Errorf("failed to format source: %v: %s", err, b.Bytes())
}
return source, nil
}
// fixConst checks untyped constant value, converting it if necessary to avoid overflow.
func fixConst(name string, val constant.Value, imports map[string]bool) string {
var (
tok string
str string
)
switch val.Kind() {
case constant.Int:
tok = "INT"
str = val.ExactString()
case constant.Float:
v := constant.Val(val) // v is *big.Rat or *big.Float
f, ok := v.(*big.Float)
if !ok {
f = new(big.Float).SetRat(v.(*big.Rat))
}
tok = "FLOAT"
str = f.Text('g', int(f.Prec()))
case constant.Complex:
// TODO: not sure how to parse this case
fallthrough
default:
return name
}
imports["go/constant"] = true
imports["go/token"] = true
return fmt.Sprintf("constant.MakeFromLiteral(\"%s\", token.%s, 0)", str, tok)
}
// importPath checks whether pkgIdent is an existing directory relative to
// e.WorkingDir. If yes, it returns the actual import path of the Go package
// located in the directory. If it is definitely a relative path, but it does not
// exist, an error is returned. Otherwise, it is assumed to be an import path, and
// pkgIdent is returned.
func (e Extractor) importPath(pkgIdent, importPath string) (string, error) {
wd, err := os.Getwd()
if err != nil {
return "", err
}
dirPath := filepath.Join(wd, pkgIdent)
_, err = os.Stat(dirPath)
if err != nil && !os.IsNotExist(err) {
return "", err
}
if err != nil {
if len(pkgIdent) > 0 && pkgIdent[0] == '.' {
// pkgIdent is definitely a relative path, not a package name, and it does not exist
return "", err
}
// pkgIdent might be a valid stdlib package name. So we leave that responsibility to the caller now.
return pkgIdent, nil
}
// local import
if importPath != "" {
return importPath, nil
}
modPath := filepath.Join(dirPath, "go.mod")
_, err = os.Stat(modPath)
if os.IsNotExist(err) {
return "", errors.New("no go.mod found, and no import path specified")
}
if err != nil {
return "", err
}
f, err := os.Open(modPath)
if err != nil {
return "", err
}
defer func() {
_ = f.Close()
}()
sc := bufio.NewScanner(f)
var l string
for sc.Scan() {
l = sc.Text()
break
}
if sc.Err() != nil {
return "", err
}
parts := strings.Fields(l)
if len(parts) < 2 {
return "", errors.New(`invalid first line syntax in go.mod`)
}
if parts[0] != "module" {
return "", errors.New(`invalid first line in go.mod, no "module" found`)
}
return parts[1], nil
}
// Extractor creates a package with all the symbols from a dependency package.
type Extractor struct {
Dest string // the name of the created package.
License string // license text to be included in the created package, optional.
Skip map[string]bool
}
// Extract writes to rw a Go package with all the symbols found at pkgIdent.
// pkgIdent can be an import path, or a local path, relative to e.WorkingDir. In
// the latter case, Extract returns the actual import path of the package found at
// pkgIdent, otherwise it just returns pkgIdent.
// If pkgIdent is an import path, it is looked up in GOPATH. Vendoring is not
// supported yet, and the behavior is only defined for GO111MODULE=off.
func (e Extractor) Extract(pkgIdent, importPath string, rw io.Writer) (string, error) {
ipp, err := e.importPath(pkgIdent, importPath)
if err != nil {
return "", err
}
pkg, err := importer.ForCompiler(token.NewFileSet(), "source", nil).Import(pkgIdent)
if err != nil {
return "", err
}
content, err := genContent(e.Dest, ipp, e.License, pkg, e.Skip)
if err != nil {
return "", err
}
if _, err := rw.Write(content); err != nil {
return "", err
}
return ipp, nil
}
// GetMinor returns the minor part of the version number.
func GetMinor(part string) string {
minor := part
index := strings.Index(minor, "beta")
if index < 0 {
index = strings.Index(minor, "rc")
}
if index > 0 {
minor = minor[:index]
}
return minor
}
func buildTags() (string, error) {
version := runtime.Version()
if version == "devel" {
return "", nil
}
parts := strings.Split(version, ".")
minorRaw := GetMinor(parts[1])
currentGoVersion := parts[0] + "." + minorRaw
minor, err := strconv.Atoi(minorRaw)
if err != nil {
return "", fmt.Errorf("failed to parse version: %v", err)
}
nextGoVersion := parts[0] + "." + strconv.Itoa(minor+1)
return currentGoVersion + ",!" + nextGoVersion, nil
}

140
extract/extract_test.go Normal file
View File

@@ -0,0 +1,140 @@
package extract
import (
"bytes"
"os"
"path"
"strings"
"testing"
)
var expectedOutput = `// Code generated by 'github.com/containous/yaegi/extract guthib.com/baz'. DO NOT EDIT.
// +build BUILD_TAGS
package bar
import (
"guthib.com/baz"
"reflect"
)
func init() {
Symbols["guthib.com/baz"] = map[string]reflect.Value{
// function, constant and variable definitions
"Hello": reflect.ValueOf(baz.Hello),
}
}
`
func init() {
buildTags, err := buildTags()
if err != nil {
panic(err)
}
expectedOutput = strings.Replace(expectedOutput, "BUILD_TAGS", buildTags, 1)
}
func TestPackages(t *testing.T) {
testCases := []struct {
desc string
moduleOn string
wd string
arg string
importPath string
expected string
contains string
dest string
}{
{
desc: "stdlib math pkg, using go/importer",
dest: "math",
arg: "math",
// We check this one because it shows both defects when we break it: the value
// gets corrupted, and the type becomes token.INT
// TODO(mpl): if the ident between key and value becomes annoying, be smarter about it.
contains: `"MaxFloat64": reflect.ValueOf(constant.MakeFromLiteral("179769313486231570814527423731704356798100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", token.FLOAT, 0)),`,
},
{
desc: "using relative path, using go.mod",
wd: "./testdata/1/src/guthib.com/bar",
arg: "../baz",
expected: expectedOutput,
},
{
desc: "using relative path, manual import path",
wd: "./testdata/2/src/guthib.com/bar",
arg: "../baz",
importPath: "guthib.com/baz",
expected: expectedOutput,
},
{
desc: "using relative path, go.mod is ignored, because manual path",
wd: "./testdata/3/src/guthib.com/bar",
arg: "../baz",
importPath: "guthib.com/baz",
expected: expectedOutput,
},
{
desc: "using relative path, dep in vendor, using go.mod",
wd: "./testdata/4/src/guthib.com/bar",
arg: "./vendor/guthib.com/baz",
expected: expectedOutput,
},
{
desc: "using relative path, dep in vendor, manual import path",
wd: "./testdata/5/src/guthib.com/bar",
arg: "./vendor/guthib.com/baz",
importPath: "guthib.com/baz",
expected: expectedOutput,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
cwd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
wd := test.wd
if wd == "" {
wd = cwd
} else {
if err := os.Chdir(wd); err != nil {
t.Fatal(err)
}
defer func() {
if err := os.Chdir(cwd); err != nil {
t.Fatal(err)
}
}()
}
dest := path.Base(wd)
if test.dest != "" {
dest = test.dest
}
ext := Extractor{
Dest: dest,
}
var out bytes.Buffer
if _, err := ext.Extract(test.arg, test.importPath, &out); err != nil {
t.Fatal(err)
}
if test.expected != "" {
if out.String() != test.expected {
t.Fatalf("\nGot:\n%q\nWant: \n%q", out.String(), test.expected)
}
}
if test.contains != "" {
if !strings.Contains(out.String(), test.contains) {
t.Fatalf("Missing expected part: %s in %s", test.contains, out.String())
}
}
})
}
}

View File

@@ -0,0 +1,9 @@
package main
import (
"guthib.com/baz"
)
func main() {
baz.Hello()
}

View File

@@ -0,0 +1,5 @@
package baz
func Hello() {
println("HELLO")
}

View File

@@ -0,0 +1,3 @@
module guthib.com/baz
go 1.14

View File

@@ -0,0 +1,9 @@
package main
import (
"guthib.com/baz"
)
func main() {
baz.Hello()
}

View File

@@ -0,0 +1,5 @@
package baz
func Hello() {
println("HELLO")
}

View File

@@ -0,0 +1,9 @@
package main
import (
"guthib.com/baz"
)
func main() {
baz.Hello()
}

View File

@@ -0,0 +1,5 @@
package baz
func Hello() {
println("HELLO")
}

View File

@@ -0,0 +1,3 @@
module should.not/be/used
go 1.14

View File

@@ -0,0 +1,9 @@
package main
import (
"guthib.com/baz"
)
func main() {
baz.Hello()
}

View File

@@ -0,0 +1,5 @@
package baz
func Hello() {
println("HELLO")
}

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