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 }