Files
moxa/interp/program.go
Marc Vertes 229ddfdae1 interp: fix use of builtins in type definitions
Make len() and cap() work on pointers. Preserve scope in case of
nested calls of cfg.

Fixes #1285.
2021-10-18 10:50:14 +02:00

193 lines
4.5 KiB
Go

package interp
import (
"context"
"go/ast"
"io/ioutil"
"reflect"
"runtime"
"runtime/debug"
)
// A Program is Go code that has been parsed and compiled.
type Program struct {
pkgName string
root *node
init []*node
}
// 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 := ioutil.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.
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
}