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 }