The recent changes that added some redeclaration checks implicitly added more strictness related to namespaces and scopes which, among other things, broke some uses that "accidentally" used to work. For example, given const script1 = ` import "fmt" // more code ` const script2 = ` import "fmt" // some other code ` If one Evals script1, then script2, with the same interpreter, without specifying any scope, as the two fragments would be considered part of the same (.go file) scope by default, a redeclaration error would be triggered because import "fmt" is seen twice. A work-around would have been to specify (a different) i.Name before each Eval call, so that each script is considered as coming from a different .go file, and hence are respectively in different scopes with respect to imports. That lead us to realize we had to make specifying things such as file-scope, and "incremental mode" (aka REPL), more obvious in the context of an Eval call. In addition, we want to lay down the foundations for Yaegi being able to behave more like the go tool wrt to various inputs, i.e. it should be able to take a package directory, or an import path, as input, instead of just a .go file. Hence the introduction of a new kind of Eval method (whose signature is not fixed yet): func (interp *Interpreter) EvalPath(path string) (res reflect.Value, err error) It partially solves the problem described above because: 1. the path given to EvalPath can be used as the file-scope hint mentioned above, for now (even though the related implementation details might change). 2. Eval always runs in incremental mode, whereas EvalPath always runs in non-incremental mode, hence clarifying the situation in that respect. And to avoid confusion, the Name field of Interpreter is now non-exported, since it is somewhat redundant with the path argument of EvalPath. Note that #731 is not fully fixed (and might never be), as a requirement of the proposed solution is to move the input bits of code into respective files (instead of leaving them as strings). Finally, some related bugfixes, documention changes, and some refactoring have been included. Notably, there is no "empty scope" anymore, i.e. name defaults to "_.go" when it is not specified. Updates #731 Fixes #778 Fixes #798 Fixes #789 Co-authored-by: Marc Vertes <mvertes@free.fr>
Yaegi is Another Elegant Go Interpreter. It powers executable Go scripts and plugins, in embedded interpreters or interactive shells, on top of the Go runtime.
Features
- Complete support of Go specification
- Written in pure Go, using only the standard library
- Simple interpreter API:
New(),Eval(),Use() - Works everywhere Go works
- All Go & runtime resources accessible from script (with control)
- Security:
unsafeandsyscallpackages neither used nor exported by default - Support Go 1.13 and Go 1.14 (the latest 2 major releases)
Install
Go package
import "github.com/containous/yaegi/interp"
Command-line executable
go get -u github.com/containous/yaegi/cmd/yaegi
Note that you can use rlwrap (install with your favorite package manager),
and alias the yaegi command in alias yaegi='rlwrap yaegi' in your ~/.bashrc, to have history and command line edition.
Usage
As an embedded interpreter
Create an interpreter with New(), run Go code with Eval():
package main
import (
"github.com/containous/yaegi/interp"
"github.com/containous/yaegi/stdlib"
)
func main() {
i := interp.New(interp.Options{})
i.Use(stdlib.Symbols)
_, err := i.Eval(`import "fmt"`)
if err != nil {
panic(err)
}
_, err = i.Eval(`fmt.Println("Hello Yaegi")`)
if err != nil {
panic(err)
}
}
As a dynamic extension framework
The following program is compiled ahead of time, except bar() which is interpreted, with the following steps:
- use of
i.Eval(src)to evaluate the script in the context of interpreter - use of
v, err := i.Eval("foo.Bar")to get the symbol from the interpreter context, as areflect.Value - application of
Interface()method and type assertion to convertvintobar, as if it was compiled
package main
import "github.com/containous/yaegi/interp"
const src = `package foo
func Bar(s string) string { return s + "-Foo" }`
func main() {
i := interp.New(interp.Options{})
_, err := i.Eval(src)
if err != nil {
panic(err)
}
v, err := i.Eval("foo.Bar")
if err != nil {
panic(err)
}
bar := v.Interface().(func(string) string)
r := bar("Kung")
println(r)
}
As a command-line interpreter
The Yaegi command can run an interactive Read-Eval-Print-Loop:
$ yaegi
> 1 + 2
3
> import "fmt"
> fmt.Println("Hello World")
Hello World
>
Or interpret Go files:
$ yaegi cmd/yaegi/yaegi.go
>
Or for Go scripting in the shebang line:
$ cat /tmp/test
#!/usr/bin/env yaegi
package main
import "fmt"
func main() {
fmt.Println("test")
}
$ ls -la /tmp/test
-rwxr-xr-x 1 dow184 dow184 93 Jan 6 13:38 /tmp/test
$ /tmp/test
test
Documentation
Documentation about Yaegi commands and libraries can be found at usual godoc.org.
Limitations
Beside the known bugs which are supposed to be fixed in the short term, there are some limitations not planned to be addressed soon:
- assembly files (
.s) are not supported - calling C code is not supported (no virtual "C" package)
- interfaces to be used from the pre-compiled code can not be added dynamically, as it is required to pre-compile interface wrappers
- representation of types by
reflectand printing values using %T may give different results between compiled mode and interpreted mode - interpreting computation intensive code is likely to remain significantly slower than in compiled mode
