I’ve been using this to execute Go “scripts” in CI pipelines where Bash just doesn’t cut it. It’s an interpreter for Go. It can be used to treat Go code like a “script,” rather than a compiled application. It is also able to be imported into a Go program and used to load up Go code dynamically at run time (think “loading plugins” with Go!).
From the readme:
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:
unsafe
andsyscall
packages neither used nor exported by default - Support the latest 2 major releases of Go (Go 1.19 and Go 1.20)
Install
Go package
import "github.com/traefik/yaegi/interp"
Command-line executable
go install github.com/traefik/yaegi/cmd/yaegi@latest
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.
CI Integration
curl -sfL https://raw.githubusercontent.com/traefik/yaegi/master/install.sh | bash -s -- -b $GOPATH/bin v0.9.0
Usage
As an embedded interpreter
Create an interpreter with New()
, run Go code with Eval()
:
package main
import (
"github.com/traefik/yaegi/interp"
"github.com/traefik/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 convertv
intobar
, as if it was compiled
package main
import "github.com/traefik/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
>
Note that in interactive mode, all stdlib package are pre-imported, you can use them directly:
$ yaegi
> reflect.TypeOf(time.Date)
: func(int, time.Month, int, int, int, int, int, *time.Location) time.Time
>
Or interpret Go packages, directories or files, including itself:
$ yaegi -syscall -unsafe -unrestricted github.com/traefik/yaegi/cmd/yaegi
>
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).
- Directives about the compiler, the linker, or embedding files are not supported.
- 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
reflect
and 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.
Go modules are not supported yet. Until that, it is necessary to install the source into $GOPATH/src/github.com/traefik/yaegi
to pass all the tests.
Contributing
License
Looks nice. But if you want to use plugins, I think Go supports compiled ones (https://pkg.go.dev/plugin). Not for Windows. Never used it, though.
This is a very cool, and valid, contribution to the conversation. Thank you!
It may be worth noting that in addition to a lack of Windows compatibility, your link has this warning towards the bottom of the page (after listing several more restrictions):
Together, these restrictions mean that, in practice, the application and its plugins must all be built together by a single person or component of a system. In that case, it may be simpler for that person or component to generate Go source files that blank-import the desired set of plugins and then compile a static executable in the usual way.
I haven’t done a lot of research into how these drawbacks compare to implementing plugins via Yaegi, but shooting from the hip with my limited experience, I think that Yaegi may be the more flexible plug-in option (for now!).
Is it possible that the interpreter also runs in the browser? Maybe via #wasm and #tinygo?
This is the dream.