github.com/switchupcb/yaegi@v0.10.2/interp/program.go (about)

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