162 lines
4.7 KiB
Go
162 lines
4.7 KiB
Go
/*
|
|
Yaegi interprets Go programs.
|
|
|
|
Yaegi reads Go language programs from standard input, string
|
|
parameters or files and run them.
|
|
|
|
If invoked with no arguments, it processes the standard input in
|
|
a Read-Eval-Print-Loop. A prompt is displayed if standard input is
|
|
a terminal.
|
|
|
|
File Mode
|
|
|
|
In file mode, as in a standard Go compiler, source files are read entirely
|
|
before being parsed, then evaluated. It allows to handle forward
|
|
declarations and to have package code split in multiple source files.
|
|
|
|
Go specifications fully apply in this mode.
|
|
|
|
All files are interpreted in file mode except the initial file if it
|
|
starts with "#!" characters (the shebang pattern to allow executable
|
|
scripts), for example "#!/usr/bin/env yaegi". In that case, the initial
|
|
file is interpreted in REPL mode.
|
|
|
|
REPL mode
|
|
|
|
In REPL mode, the interpreter parses the code incrementally. As soon
|
|
as a statement is complete, it evaluates it. This makes the interpreter
|
|
suitable for interactive command line and scripts.
|
|
|
|
Go specifications apply with the following differences:
|
|
|
|
All local and global declarations (const, var, type, func) are allowed,
|
|
including in short form, except that all identifiers must be defined
|
|
before use (as declarations inside a standard Go function).
|
|
|
|
The statements are evaluated in the global space, within an implicit
|
|
"main" package.
|
|
|
|
It is not necessary to have a package statement, or a main function in
|
|
REPL mode. Import statements for preloaded binary packages can also
|
|
be avoided (i.e. all the standard library except the few packages
|
|
where default names collide, as "math/rand" and "crypto/rand", for which
|
|
an explicit import is still necessary).
|
|
|
|
Note that the source packages are always interpreted in file mode,
|
|
even if imported from REPL.
|
|
|
|
The following extract is a valid executable script:
|
|
|
|
#!/usr/bin/env yaegi
|
|
helloHandler := func(w http.ResponseWriter, req *http.Request) {
|
|
io.WriteString(w, "Hello, world!\n")
|
|
}
|
|
http.HandleFunc("/hello", helloHandler)
|
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
|
|
Example of a one liner:
|
|
|
|
$ yaegi -e 'println(reflect.TypeOf(fmt.Print))'
|
|
|
|
Options:
|
|
-e string
|
|
evaluate the string and return.
|
|
-i
|
|
start an interactive REPL after file execution.
|
|
-tags tag,list
|
|
a comma-separated list of build tags to consider satisfied during
|
|
the interpretation.
|
|
|
|
Debugging support (may be removed at any time):
|
|
YAEGI_AST_DOT=1
|
|
Generate and display graphviz dot of AST with dotty(1)
|
|
YAEGI_CFG_DOT=1
|
|
Generate and display graphviz dot of CFG with dotty(1)
|
|
YAEGI_DOT_CMD='dot -Tsvg -ofoo.svg'
|
|
Defines how to process the dot code generated whenever YAEGI_AST_DOT and/or
|
|
YAEGI_CFG_DOT is enabled. If any of YAEGI_AST_DOT or YAEGI_CFG_DOT is set,
|
|
but YAEGI_DOT_CMD is not defined, the default is to write to a .dot file
|
|
next to the Go source file.
|
|
*/
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"go/build"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/containous/yaegi/interp"
|
|
"github.com/containous/yaegi/stdlib"
|
|
"github.com/containous/yaegi/stdlib/unsafe"
|
|
)
|
|
|
|
func main() {
|
|
var interactive bool
|
|
var useUnsafe bool
|
|
var tags string
|
|
var cmd string
|
|
flag.BoolVar(&interactive, "i", false, "start an interactive REPL")
|
|
flag.BoolVar(&useUnsafe, "unsafe", false, "include usafe symbols")
|
|
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)
|
|
|
|
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
|
|
i.Use(stdlib.Symbols)
|
|
i.Use(interp.Symbols)
|
|
if useUnsafe {
|
|
i.Use(unsafe.Symbols)
|
|
}
|
|
|
|
if cmd != `` {
|
|
i.REPL(strings.NewReader(cmd), os.Stderr)
|
|
}
|
|
|
|
if len(args) == 0 {
|
|
if interactive || cmd == `` {
|
|
i.REPL(os.Stdin, os.Stdout)
|
|
}
|
|
return
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|