The interpreter has its own internal fileset and expects all code to have been parsed using that fileset. If a user creates a fileset, calls `go/parser.Parse*`, then passes the result to `interp.CompileAST`, strange things can happen. The solutions I can see are: 1. Expose the fileset so the user can use it when parsing source. 2. Add the fileset as an option (input to New) so that the user can tell the interpreter to use a specific fileset. 3. Pass the fileset into `CompileAST` There are two ways to implement option 3. One is to add a field to nodes and update every reference to `interp.fset` to use `node.fset`. The other is to add a parameter to every function that references `interp.fset` or calls a function that does. Both of these are significant changes and involve an extra pointer for every node or most function calls. Options 1 and 2 are easy. Option 2 involves adding an option so I went with option 1. I can imagine situations where option 2 could be necessary, but I can open another issue/PR if and when I need that. Fixes #1383
203 lines
4.7 KiB
Go
203 lines
4.7 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
|
|
}
|
|
|
|
// 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
|
|
}
|