package interp
import (
"bufio"
"context"
"errors"
"fmt"
"go/build"
"go/scanner"
"go/token"
"io"
"io/fs"
"os"
"os/signal"
"path"
"path/filepath"
"reflect"
"runtime"
"runtime/debug"
"strconv"
"strings"
"sync"
"sync/atomic"
)
type node struct {
debug *nodeDebugData
child []*node
anc *node
param []*itype
start *node
tnext *node
fnext *node
interp *Interpreter
index int64
findex int
level int
nleft int
nright int
kind nkind
pos token .Pos
sym *symbol
typ *itype
recv *receiver
types []reflect .Type
scope *scope
action action
exec bltn
gen bltnGenerator
val interface {}
rval reflect .Value
ident string
redeclared bool
meta interface {}
}
func (n *node ) shouldBreak () bool {
if n == nil || n .debug == nil {
return false
}
if n .debug .breakOnLine || n .debug .breakOnCall {
return true
}
return false
}
func (n *node ) setProgram (p *Program ) {
if n .debug == nil {
n .debug = new (nodeDebugData )
}
n .debug .program = p
}
func (n *node ) setBreakOnCall (v bool ) {
if n .debug == nil {
if !v {
return
}
n .debug = new (nodeDebugData )
}
n .debug .breakOnCall = v
}
func (n *node ) setBreakOnLine (v bool ) {
if n .debug == nil {
if !v {
return
}
n .debug = new (nodeDebugData )
}
n .debug .breakOnLine = v
}
type receiver struct {
node *node
val reflect .Value
index []int
}
type frame struct {
id uint64
debug *frameDebugData
root *frame
anc *frame
data []reflect .Value
mutex sync .RWMutex
deferred [][]reflect .Value
recovered interface {}
done reflect .SelectCase
}
func newFrame(anc *frame , length int , id uint64 ) *frame {
f := &frame {
anc : anc ,
data : make ([]reflect .Value , length ),
id : id ,
}
if anc == nil {
f .root = f
} else {
f .done = anc .done
f .root = anc .root
}
return f
}
func (f *frame ) runid () uint64 { return atomic .LoadUint64 (&f .id ) }
func (f *frame ) setrunid (id uint64 ) { atomic .StoreUint64 (&f .id , id ) }
func (f *frame ) clone () *frame {
f .mutex .RLock ()
defer f .mutex .RUnlock ()
nf := &frame {
anc : f .anc ,
root : f .root ,
deferred : f .deferred ,
recovered : f .recovered ,
id : f .runid (),
done : f .done ,
debug : f .debug ,
}
nf .data = make ([]reflect .Value , len (f .data ))
copy (nf .data , f .data )
return nf
}
type Exports map [string ]map [string ]reflect .Value
type imports map [string ]map [string ]*symbol
type opt struct {
dotCmd string
context build .Context
stdin io .Reader
stdout io .Writer
stderr io .Writer
args []string
env map [string ]string
filesystem fs .FS
astDot bool
cfgDot bool
noRun bool
fastChan bool
specialStdio bool
unrestricted bool
}
type Interpreter struct {
id uint64
nindex int64
name string
opt
cancelChan bool
fset *token .FileSet
binPkg Exports
rdir map [string ]bool
mapTypes map [reflect .Value ][]reflect .Type
mutex sync .RWMutex
frame *frame
universe *scope
scopes map [string ]*scope
srcPkg imports
pkgNames map [string ]string
done chan struct {}
roots []*node
generic map [string ]*node
hooks *hooks
debugger *Debugger
}
const (
mainID = "main"
selfPrefix = "github.com/traefik/yaegi"
selfPath = selfPrefix + "/interp/interp"
DefaultSourceName = "_.go"
Test = false
NoTest = true
)
var Self *Interpreter
var Symbols = Exports {
selfPath : map [string ]reflect .Value {
"New" : reflect .ValueOf (New ),
"Interpreter" : reflect .ValueOf ((*Interpreter )(nil )),
"Options" : reflect .ValueOf ((*Options )(nil )),
"Panic" : reflect .ValueOf ((*Panic )(nil )),
},
}
func init() { Symbols [selfPath ]["Symbols" ] = reflect .ValueOf (Symbols ) }
type _error struct {
IValue interface {}
WError func () string
}
func (w _error ) Error () string { return w .WError () }
type Panic struct {
Value interface {}
Callers []uintptr
Stack []byte
}
func (e Panic ) Error () string { return fmt .Sprint (e .Value ) }
func (n *node ) Walk (in func (n *node ) bool , out func (n *node )) {
if in != nil && !in (n ) {
return
}
for _ , child := range n .child {
child .Walk (in , out )
}
if out != nil {
out (n )
}
}
type Options struct {
GoPath string
BuildTags []string
Stdin io .Reader
Stdout, Stderr io .Writer
Args []string
Env []string
SourcecodeFilesystem fs .FS
Unrestricted bool
}
func New (options Options ) *Interpreter {
i := Interpreter {
opt : opt {context : build .Default , filesystem : &realFS {}, env : map [string ]string {}},
frame : newFrame (nil , 0 , 0 ),
fset : token .NewFileSet (),
universe : initUniverse (),
scopes : map [string ]*scope {},
binPkg : Exports {"" : map [string ]reflect .Value {"_error" : reflect .ValueOf ((*_error )(nil ))}},
mapTypes : map [reflect .Value ][]reflect .Type {},
srcPkg : imports {},
pkgNames : map [string ]string {},
rdir : map [string ]bool {},
hooks : &hooks {},
generic : map [string ]*node {},
}
if i .opt .stdin = options .Stdin ; i .opt .stdin == nil {
i .opt .stdin = os .Stdin
}
if i .opt .stdout = options .Stdout ; i .opt .stdout == nil {
i .opt .stdout = os .Stdout
}
if i .opt .stderr = options .Stderr ; i .opt .stderr == nil {
i .opt .stderr = os .Stderr
}
if i .opt .args = options .Args ; i .opt .args == nil {
i .opt .args = os .Args
}
if options .Unrestricted {
i .opt .unrestricted = true
} else {
for _ , e := range options .Env {
a := strings .SplitN (e , "=" , 2 )
if len (a ) == 2 {
i .opt .env [a [0 ]] = a [1 ]
} else {
i .opt .env [a [0 ]] = ""
}
}
}
if options .SourcecodeFilesystem != nil {
i .opt .filesystem = options .SourcecodeFilesystem
}
i .opt .context .GOPATH = options .GoPath
if len (options .BuildTags ) > 0 {
i .opt .context .BuildTags = options .BuildTags
}
i .opt .astDot , _ = strconv .ParseBool (os .Getenv ("YAEGI_AST_DOT" ))
i .opt .cfgDot , _ = strconv .ParseBool (os .Getenv ("YAEGI_CFG_DOT" ))
i .opt .dotCmd = os .Getenv ("YAEGI_DOT_CMD" )
i .opt .noRun , _ = strconv .ParseBool (os .Getenv ("YAEGI_NO_RUN" ))
i .opt .fastChan , _ = strconv .ParseBool (os .Getenv ("YAEGI_FAST_CHAN" ))
i .opt .specialStdio , _ = strconv .ParseBool (os .Getenv ("YAEGI_SPECIAL_STDIO" ))
return &i
}
const (
bltnAlignof = "unsafe.Alignof"
bltnAppend = "append"
bltnCap = "cap"
bltnClose = "close"
bltnComplex = "complex"
bltnImag = "imag"
bltnCopy = "copy"
bltnDelete = "delete"
bltnLen = "len"
bltnMake = "make"
bltnNew = "new"
bltnOffsetof = "unsafe.Offsetof"
bltnPanic = "panic"
bltnPrint = "print"
bltnPrintln = "println"
bltnReal = "real"
bltnRecover = "recover"
bltnSizeof = "unsafe.Sizeof"
)
func initUniverse() *scope {
sc := &scope {global : true , sym : map [string ]*symbol {
"any" : {kind : typeSym , typ : &itype {cat : interfaceT , str : "any" }},
"bool" : {kind : typeSym , typ : &itype {cat : boolT , name : "bool" , str : "bool" }},
"byte" : {kind : typeSym , typ : &itype {cat : uint8T , name : "uint8" , str : "uint8" }},
"comparable" : {kind : typeSym , typ : &itype {cat : comparableT , name : "comparable" , str : "comparable" }},
"complex64" : {kind : typeSym , typ : &itype {cat : complex64T , name : "complex64" , str : "complex64" }},
"complex128" : {kind : typeSym , typ : &itype {cat : complex128T , name : "complex128" , str : "complex128" }},
"error" : {kind : typeSym , typ : &itype {cat : errorT , name : "error" , str : "error" }},
"float32" : {kind : typeSym , typ : &itype {cat : float32T , name : "float32" , str : "float32" }},
"float64" : {kind : typeSym , typ : &itype {cat : float64T , name : "float64" , str : "float64" }},
"int" : {kind : typeSym , typ : &itype {cat : intT , name : "int" , str : "int" }},
"int8" : {kind : typeSym , typ : &itype {cat : int8T , name : "int8" , str : "int8" }},
"int16" : {kind : typeSym , typ : &itype {cat : int16T , name : "int16" , str : "int16" }},
"int32" : {kind : typeSym , typ : &itype {cat : int32T , name : "int32" , str : "int32" }},
"int64" : {kind : typeSym , typ : &itype {cat : int64T , name : "int64" , str : "int64" }},
"interface{}" : {kind : typeSym , typ : &itype {cat : interfaceT , str : "interface{}" }},
"rune" : {kind : typeSym , typ : &itype {cat : int32T , name : "int32" , str : "int32" }},
"string" : {kind : typeSym , typ : &itype {cat : stringT , name : "string" , str : "string" }},
"uint" : {kind : typeSym , typ : &itype {cat : uintT , name : "uint" , str : "uint" }},
"uint8" : {kind : typeSym , typ : &itype {cat : uint8T , name : "uint8" , str : "uint8" }},
"uint16" : {kind : typeSym , typ : &itype {cat : uint16T , name : "uint16" , str : "uint16" }},
"uint32" : {kind : typeSym , typ : &itype {cat : uint32T , name : "uint32" , str : "uint32" }},
"uint64" : {kind : typeSym , typ : &itype {cat : uint64T , name : "uint64" , str : "uint64" }},
"uintptr" : {kind : typeSym , typ : &itype {cat : uintptrT , name : "uintptr" , str : "uintptr" }},
"false" : {kind : constSym , typ : untypedBool (nil ), rval : reflect .ValueOf (false )},
"true" : {kind : constSym , typ : untypedBool (nil ), rval : reflect .ValueOf (true )},
"iota" : {kind : constSym , typ : untypedInt (nil )},
"nil" : {typ : &itype {cat : nilT , untyped : true , str : "nil" }},
bltnAppend : {kind : bltnSym , builtin : _append },
bltnCap : {kind : bltnSym , builtin : _cap },
bltnClose : {kind : bltnSym , builtin : _close },
bltnComplex : {kind : bltnSym , builtin : _complex },
bltnImag : {kind : bltnSym , builtin : _imag },
bltnCopy : {kind : bltnSym , builtin : _copy },
bltnDelete : {kind : bltnSym , builtin : _delete },
bltnLen : {kind : bltnSym , builtin : _len },
bltnMake : {kind : bltnSym , builtin : _make },
bltnNew : {kind : bltnSym , builtin : _new },
bltnPanic : {kind : bltnSym , builtin : _panic },
bltnPrint : {kind : bltnSym , builtin : _print },
bltnPrintln : {kind : bltnSym , builtin : _println },
bltnReal : {kind : bltnSym , builtin : _real },
bltnRecover : {kind : bltnSym , builtin : _recover },
}}
return sc
}
func (interp *Interpreter ) resizeFrame () {
l := len (interp .universe .types )
b := len (interp .frame .data )
if l -b <= 0 {
return
}
data := make ([]reflect .Value , l )
copy (data , interp .frame .data )
for j , t := range interp .universe .types [b :] {
data [b +j ] = reflect .New (t ).Elem ()
}
interp .frame .data = data
}
func (interp *Interpreter ) Eval (src string ) (res reflect .Value , err error ) {
return interp .eval (src , "" , true )
}
func (interp *Interpreter ) EvalPath (path string ) (res reflect .Value , err error ) {
if !isFile (interp .opt .filesystem , path ) {
_ , err := interp .importSrc (mainID , path , NoTest )
return res , err
}
b , err := fs .ReadFile (interp .filesystem , path )
if err != nil {
return res , err
}
return interp .eval (string (b ), path , false )
}
func (interp *Interpreter ) EvalPathWithContext (ctx context .Context , path string ) (res reflect .Value , err error ) {
interp .mutex .Lock ()
interp .done = make (chan struct {})
interp .cancelChan = !interp .opt .fastChan
interp .mutex .Unlock ()
done := make (chan struct {})
go func () {
defer close (done )
res , err = interp .EvalPath (path )
}()
select {
case <- ctx .Done ():
interp .stop ()
return reflect .Value {}, ctx .Err ()
case <- done :
}
return res , err
}
func (interp *Interpreter ) EvalTest (path string ) error {
_ , err := interp .importSrc (mainID , path , Test )
return err
}
func isFile(filesystem fs .FS , path string ) bool {
fi , err := fs .Stat (filesystem , path )
return err == nil && fi .Mode ().IsRegular ()
}
func (interp *Interpreter ) eval (src , name string , inc bool ) (res reflect .Value , err error ) {
prog , err := interp .compileSrc (src , name , inc )
if err != nil {
return res , err
}
if interp .noRun {
return res , err
}
return interp .Execute (prog )
}
func (interp *Interpreter ) EvalWithContext (ctx context .Context , src string ) (reflect .Value , error ) {
var v reflect .Value
var err error
interp .mutex .Lock ()
interp .done = make (chan struct {})
interp .cancelChan = !interp .opt .fastChan
interp .mutex .Unlock ()
done := make (chan struct {})
go func () {
defer func () {
if r := recover (); r != nil {
var pc [64 ]uintptr
n := runtime .Callers (1 , pc [:])
err = Panic {Value : r , Callers : pc [:n ], Stack : debug .Stack ()}
}
close (done )
}()
v , err = interp .Eval (src )
}()
select {
case <- ctx .Done ():
interp .stop ()
return reflect .Value {}, ctx .Err ()
case <- done :
}
return v , err
}
func (interp *Interpreter ) stop () {
atomic .AddUint64 (&interp .id , 1 )
close (interp .done )
}
func (interp *Interpreter ) runid () uint64 { return atomic .LoadUint64 (&interp .id ) }
func ignoreScannerError(e *scanner .Error , s string ) bool {
msg := e .Msg
if strings .HasSuffix (msg , "found 'EOF'" ) {
return true
}
if msg == "raw string literal not terminated" {
return true
}
if strings .HasPrefix (msg , "expected operand, found '}'" ) && !strings .HasSuffix (s , "}" ) {
return true
}
return false
}
func (interp *Interpreter ) ImportUsed () {
sc := interp .universe
for k := range interp .binPkg {
name := path .Base (k )
if sym , ok := sc .sym [name ]; ok {
name2 := key2name (fixKey (sym .typ .path ))
sc .sym [name2 ] = sym
if name2 != name {
delete (sc .sym , name )
}
name = key2name (fixKey (k ))
}
sc .sym [name ] = &symbol {kind : pkgSym , typ : &itype {cat : binPkgT , path : k , scope : sc }}
}
}
func key2name(name string ) string {
return filepath .Join (name , DefaultSourceName )
}
func fixKey(k string ) string {
i := strings .LastIndex (k , "/" )
if i >= 0 {
k = k [:i ] + "_" + k [i +1 :]
}
return k
}
func (interp *Interpreter ) REPL () (reflect .Value , error ) {
in , out , errs := interp .stdin , interp .stdout , interp .stderr
ctx , cancel := context .WithCancel (context .Background ())
end := make (chan struct {})
sig := make (chan os .Signal , 1 )
lines := make (chan string )
prompt := getPrompt (in , out )
s := bufio .NewScanner (in )
var v reflect .Value
var err error
src := ""
signal .Notify (sig , os .Interrupt )
defer signal .Stop (sig )
prompt (v )
go func () {
defer close (end )
for s .Scan () {
lines <- s .Text ()
}
if e := s .Err (); e != nil {
fmt .Fprintln (errs , e )
}
}()
go func () {
for {
select {
case <- sig :
cancel ()
lines <- ""
case <- end :
return
}
}
}()
for {
var line string
select {
case <- end :
cancel ()
return v , err
case line = <- lines :
src += line + "\n"
}
v , err = interp .EvalWithContext (ctx , src )
if err != nil {
switch e := err .(type ) {
case scanner .ErrorList :
if len (e ) > 0 && ignoreScannerError (e [0 ], line ) {
continue
}
fmt .Fprintln (errs , strings .TrimPrefix (e [0 ].Error (), DefaultSourceName +":" ))
case Panic :
fmt .Fprintln (errs , e .Value )
fmt .Fprintln (errs , string (e .Stack ))
default :
fmt .Fprintln (errs , err )
}
}
if errors .Is (err , context .Canceled ) {
ctx , cancel = context .WithCancel (context .Background ())
}
src = ""
prompt (v )
}
}
func doPrompt(out io .Writer ) func (v reflect .Value ) {
return func (v reflect .Value ) {
if v .IsValid () {
fmt .Fprintln (out , ":" , v )
}
fmt .Fprint (out , "> " )
}
}
func getPrompt(in io .Reader , out io .Writer ) func (reflect .Value ) {
forcePrompt , _ := strconv .ParseBool (os .Getenv ("YAEGI_PROMPT" ))
if forcePrompt {
return doPrompt (out )
}
s , ok := in .(interface { Stat () (os .FileInfo , error ) })
if !ok {
return func (reflect .Value ) {}
}
stat , err := s .Stat ()
if err == nil && stat .Mode ()&os .ModeCharDevice != 0 {
return doPrompt (out )
}
return func (reflect .Value ) {}
}
The pages are generated with Golds v0.8.4 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .