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