github.com/traefik/yaegi@v0.15.1/interp/program.go (about)

     1  package interp
     2  
     3  import (
     4  	"context"
     5  	"go/ast"
     6  	"go/token"
     7  	"os"
     8  	"reflect"
     9  	"runtime"
    10  	"runtime/debug"
    11  )
    12  
    13  // A Program is Go code that has been parsed and compiled.
    14  type Program struct {
    15  	pkgName string
    16  	root    *node
    17  	init    []*node
    18  }
    19  
    20  // PackageName returns name used in a package clause.
    21  func (p *Program) PackageName() string {
    22  	return p.pkgName
    23  }
    24  
    25  // FileSet is the fileset that must be used for parsing Go that will be passed
    26  // to interp.CompileAST().
    27  func (interp *Interpreter) FileSet() *token.FileSet {
    28  	return interp.fset
    29  }
    30  
    31  // Compile parses and compiles a Go code represented as a string.
    32  func (interp *Interpreter) Compile(src string) (*Program, error) {
    33  	return interp.compileSrc(src, "", true)
    34  }
    35  
    36  // CompilePath parses and compiles a Go code located at the given path.
    37  func (interp *Interpreter) CompilePath(path string) (*Program, error) {
    38  	if !isFile(interp.filesystem, path) {
    39  		_, err := interp.importSrc(mainID, path, NoTest)
    40  		return nil, err
    41  	}
    42  
    43  	b, err := os.ReadFile(path)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  	return interp.compileSrc(string(b), path, false)
    48  }
    49  
    50  func (interp *Interpreter) compileSrc(src, name string, inc bool) (*Program, error) {
    51  	if name != "" {
    52  		interp.name = name
    53  	}
    54  	if interp.name == "" {
    55  		interp.name = DefaultSourceName
    56  	}
    57  
    58  	// Parse source to AST.
    59  	n, err := interp.parse(src, interp.name, inc)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	return interp.CompileAST(n)
    65  }
    66  
    67  // CompileAST builds a Program for the given Go code AST. Files and block
    68  // statements can be compiled, as can most expressions. Var declaration nodes
    69  // cannot be compiled.
    70  //
    71  // WARNING: The node must have been parsed using interp.FileSet(). Results are
    72  // unpredictable otherwise.
    73  func (interp *Interpreter) CompileAST(n ast.Node) (*Program, error) {
    74  	// Convert AST.
    75  	pkgName, root, err := interp.ast(n)
    76  	if err != nil || root == nil {
    77  		return nil, err
    78  	}
    79  
    80  	if interp.astDot {
    81  		dotCmd := interp.dotCmd
    82  		if dotCmd == "" {
    83  			dotCmd = defaultDotCmd(interp.name, "yaegi-ast-")
    84  		}
    85  		root.astDot(dotWriter(dotCmd), interp.name)
    86  		if interp.noRun {
    87  			return nil, err
    88  		}
    89  	}
    90  
    91  	// Perform global types analysis.
    92  	if err = interp.gtaRetry([]*node{root}, pkgName, pkgName); err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	// Annotate AST with CFG informations.
    97  	initNodes, err := interp.cfg(root, nil, pkgName, pkgName)
    98  	if err != nil {
    99  		if interp.cfgDot {
   100  			dotCmd := interp.dotCmd
   101  			if dotCmd == "" {
   102  				dotCmd = defaultDotCmd(interp.name, "yaegi-cfg-")
   103  			}
   104  			root.cfgDot(dotWriter(dotCmd))
   105  		}
   106  		return nil, err
   107  	}
   108  
   109  	if root.kind != fileStmt {
   110  		// REPL may skip package statement.
   111  		setExec(root.start)
   112  	}
   113  	interp.mutex.Lock()
   114  	gs := interp.scopes[pkgName]
   115  	if interp.universe.sym[pkgName] == nil {
   116  		// Make the package visible under a path identical to its name.
   117  		interp.srcPkg[pkgName] = gs.sym
   118  		interp.universe.sym[pkgName] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT, path: pkgName}}
   119  		interp.pkgNames[pkgName] = pkgName
   120  	}
   121  	interp.mutex.Unlock()
   122  
   123  	// Add main to list of functions to run, after all inits.
   124  	if m := gs.sym[mainID]; pkgName == mainID && m != nil {
   125  		initNodes = append(initNodes, m.node)
   126  	}
   127  
   128  	if interp.cfgDot {
   129  		dotCmd := interp.dotCmd
   130  		if dotCmd == "" {
   131  			dotCmd = defaultDotCmd(interp.name, "yaegi-cfg-")
   132  		}
   133  		root.cfgDot(dotWriter(dotCmd))
   134  	}
   135  
   136  	return &Program{pkgName, root, initNodes}, nil
   137  }
   138  
   139  // Execute executes compiled Go code.
   140  func (interp *Interpreter) Execute(p *Program) (res reflect.Value, err error) {
   141  	defer func() {
   142  		r := recover()
   143  		if r != nil {
   144  			var pc [64]uintptr // 64 frames should be enough.
   145  			n := runtime.Callers(1, pc[:])
   146  			err = Panic{Value: r, Callers: pc[:n], Stack: debug.Stack()}
   147  		}
   148  	}()
   149  
   150  	// Generate node exec closures.
   151  	if err = genRun(p.root); err != nil {
   152  		return res, err
   153  	}
   154  
   155  	// Init interpreter execution memory frame.
   156  	interp.frame.setrunid(interp.runid())
   157  	interp.frame.mutex.Lock()
   158  	interp.resizeFrame()
   159  	interp.frame.mutex.Unlock()
   160  
   161  	// Execute node closures.
   162  	interp.run(p.root, nil)
   163  
   164  	// Wire and execute global vars.
   165  	n, err := genGlobalVars([]*node{p.root}, interp.scopes[p.pkgName])
   166  	if err != nil {
   167  		return res, err
   168  	}
   169  	interp.run(n, nil)
   170  
   171  	for _, n := range p.init {
   172  		interp.run(n, interp.frame)
   173  	}
   174  	v := genValue(p.root)
   175  	res = v(interp.frame)
   176  
   177  	// If result is an interpreter node, wrap it in a runtime callable function.
   178  	if res.IsValid() {
   179  		if n, ok := res.Interface().(*node); ok {
   180  			res = genFunctionWrapper(n)(interp.frame)
   181  		}
   182  	}
   183  
   184  	return res, err
   185  }
   186  
   187  // ExecuteWithContext executes compiled Go code.
   188  func (interp *Interpreter) ExecuteWithContext(ctx context.Context, p *Program) (res reflect.Value, err error) {
   189  	interp.mutex.Lock()
   190  	interp.done = make(chan struct{})
   191  	interp.cancelChan = !interp.opt.fastChan
   192  	interp.mutex.Unlock()
   193  
   194  	done := make(chan struct{})
   195  	go func() {
   196  		defer close(done)
   197  		res, err = interp.Execute(p)
   198  	}()
   199  
   200  	select {
   201  	case <-ctx.Done():
   202  		interp.stop()
   203  		return reflect.Value{}, ctx.Err()
   204  	case <-done:
   205  	}
   206  	return res, err
   207  }