Exposing package name of the compiled source allows using it in `Eval`, as well as in logs and for other purposes.
208 lines
4.9 KiB
Go
208 lines
4.9 KiB
Go
package interp
|
|
|
|
import (
|
|
"context"
|
|
"go/ast"
|
|
"go/token"
|
|
"os"
|
|
"reflect"
|
|
"runtime"
|
|
"runtime/debug"
|
|
)
|
|
|
|
// A Program is Go code that has been parsed and compiled.
|
|
type Program struct {
|
|
pkgName string
|
|
root *node
|
|
init []*node
|
|
}
|
|
|
|
// PackageName returns name used in a package clause.
|
|
func (p *Program) PackageName() string {
|
|
return p.pkgName
|
|
}
|
|
|
|
// FileSet is the fileset that must be used for parsing Go that will be passed
|
|
// to interp.CompileAST().
|
|
func (interp *Interpreter) FileSet() *token.FileSet {
|
|
return interp.fset
|
|
}
|
|
|
|
// Compile parses and compiles a Go code represented as a string.
|
|
func (interp *Interpreter) Compile(src string) (*Program, error) {
|
|
return interp.compileSrc(src, "", true)
|
|
}
|
|
|
|
// CompilePath parses and compiles a Go code located at the given path.
|
|
func (interp *Interpreter) CompilePath(path string) (*Program, error) {
|
|
if !isFile(interp.filesystem, path) {
|
|
_, err := interp.importSrc(mainID, path, NoTest)
|
|
return nil, err
|
|
}
|
|
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return interp.compileSrc(string(b), path, false)
|
|
}
|
|
|
|
func (interp *Interpreter) compileSrc(src, name string, inc bool) (*Program, error) {
|
|
if name != "" {
|
|
interp.name = name
|
|
}
|
|
if interp.name == "" {
|
|
interp.name = DefaultSourceName
|
|
}
|
|
|
|
// Parse source to AST.
|
|
n, err := interp.parse(src, interp.name, inc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return interp.CompileAST(n)
|
|
}
|
|
|
|
// CompileAST builds a Program for the given Go code AST. Files and block
|
|
// statements can be compiled, as can most expressions. Var declaration nodes
|
|
// cannot be compiled.
|
|
//
|
|
// WARNING: The node must have been parsed using interp.FileSet(). Results are
|
|
// unpredictable otherwise.
|
|
func (interp *Interpreter) CompileAST(n ast.Node) (*Program, error) {
|
|
// Convert AST.
|
|
pkgName, root, err := interp.ast(n)
|
|
if err != nil || root == nil {
|
|
return nil, err
|
|
}
|
|
|
|
if interp.astDot {
|
|
dotCmd := interp.dotCmd
|
|
if dotCmd == "" {
|
|
dotCmd = defaultDotCmd(interp.name, "yaegi-ast-")
|
|
}
|
|
root.astDot(dotWriter(dotCmd), interp.name)
|
|
if interp.noRun {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Perform global types analysis.
|
|
if err = interp.gtaRetry([]*node{root}, pkgName, pkgName); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Annotate AST with CFG informations.
|
|
initNodes, err := interp.cfg(root, nil, pkgName, pkgName)
|
|
if err != nil {
|
|
if interp.cfgDot {
|
|
dotCmd := interp.dotCmd
|
|
if dotCmd == "" {
|
|
dotCmd = defaultDotCmd(interp.name, "yaegi-cfg-")
|
|
}
|
|
root.cfgDot(dotWriter(dotCmd))
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
if root.kind != fileStmt {
|
|
// REPL may skip package statement.
|
|
setExec(root.start)
|
|
}
|
|
interp.mutex.Lock()
|
|
gs := interp.scopes[pkgName]
|
|
if interp.universe.sym[pkgName] == nil {
|
|
// Make the package visible under a path identical to its name.
|
|
interp.srcPkg[pkgName] = gs.sym
|
|
interp.universe.sym[pkgName] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT, path: pkgName}}
|
|
interp.pkgNames[pkgName] = pkgName
|
|
}
|
|
interp.mutex.Unlock()
|
|
|
|
// Add main to list of functions to run, after all inits.
|
|
if m := gs.sym[mainID]; pkgName == mainID && m != nil {
|
|
initNodes = append(initNodes, m.node)
|
|
}
|
|
|
|
if interp.cfgDot {
|
|
dotCmd := interp.dotCmd
|
|
if dotCmd == "" {
|
|
dotCmd = defaultDotCmd(interp.name, "yaegi-cfg-")
|
|
}
|
|
root.cfgDot(dotWriter(dotCmd))
|
|
}
|
|
|
|
return &Program{pkgName, root, initNodes}, nil
|
|
}
|
|
|
|
// Execute executes compiled Go code.
|
|
func (interp *Interpreter) Execute(p *Program) (res reflect.Value, err error) {
|
|
defer func() {
|
|
r := recover()
|
|
if r != nil {
|
|
var pc [64]uintptr // 64 frames should be enough.
|
|
n := runtime.Callers(1, pc[:])
|
|
err = Panic{Value: r, Callers: pc[:n], Stack: debug.Stack()}
|
|
}
|
|
}()
|
|
|
|
// Generate node exec closures.
|
|
if err = genRun(p.root); err != nil {
|
|
return res, err
|
|
}
|
|
|
|
// Init interpreter execution memory frame.
|
|
interp.frame.setrunid(interp.runid())
|
|
interp.frame.mutex.Lock()
|
|
interp.resizeFrame()
|
|
interp.frame.mutex.Unlock()
|
|
|
|
// Execute node closures.
|
|
interp.run(p.root, nil)
|
|
|
|
// Wire and execute global vars.
|
|
n, err := genGlobalVars([]*node{p.root}, interp.scopes[p.pkgName])
|
|
if err != nil {
|
|
return res, err
|
|
}
|
|
interp.run(n, nil)
|
|
|
|
for _, n := range p.init {
|
|
interp.run(n, interp.frame)
|
|
}
|
|
v := genValue(p.root)
|
|
res = v(interp.frame)
|
|
|
|
// If result is an interpreter node, wrap it in a runtime callable function.
|
|
if res.IsValid() {
|
|
if n, ok := res.Interface().(*node); ok {
|
|
res = genFunctionWrapper(n)(interp.frame)
|
|
}
|
|
}
|
|
|
|
return res, err
|
|
}
|
|
|
|
// ExecuteWithContext executes compiled Go code.
|
|
func (interp *Interpreter) ExecuteWithContext(ctx context.Context, p *Program) (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.Execute(p)
|
|
}()
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
interp.stop()
|
|
return reflect.Value{}, ctx.Err()
|
|
case <-done:
|
|
}
|
|
return res, err
|
|
}
|