github.com/goplus/gossa@v0.3.25/repl.go (about) 1 package gossa 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/scanner" 7 "go/token" 8 "go/types" 9 "strings" 10 11 "golang.org/x/tools/go/ssa" 12 ) 13 14 /* 15 package main 16 17 import "fmt" 18 19 const/var/type/func 20 21 func main(){} 22 */ 23 24 type Repl struct { 25 ctx *Context 26 fset *token.FileSet 27 pkg *ssa.Package 28 frame *frame // last frame 29 pc int // last pc 30 fsInit *fnState // func init 31 fsMain *fnState // func main 32 imports []string // import lines 33 globals []string // global var/func/type 34 infuncs []string // in main func 35 source string // all source 36 lastDump []string // last __gossa_repl_dump__ 37 globalMap map[string]interface{} 38 lastInterp *Interp 39 fileName string 40 } 41 42 func toDump(i []interface{}) (dump []string) { 43 for _, v := range i { 44 dump = append(dump, fmt.Sprintf("%v", v)) 45 } 46 return 47 } 48 49 func NewRepl(ctx *Context) *Repl { 50 r := &Repl{ 51 ctx: ctx, 52 fset: token.NewFileSet(), 53 globalMap: make(map[string]interface{}), 54 } 55 ctx.evalMode = true 56 ctx.evalInit = make(map[string]bool) 57 58 ctx.SetOverrideFunction("main.__gossa_repl_dump__", func(v ...interface{}) { 59 r.lastDump = toDump(v) 60 }) 61 ctx.evalCallFn = func(call *ssa.Call, res ...interface{}) { 62 if strings.HasPrefix(call.Call.Value.Name(), "__gossa_repl_") { 63 return 64 } 65 r.lastDump = toDump(res) 66 } 67 return r 68 } 69 70 func (r *Repl) SetFileName(filename string) { 71 r.fileName = filename 72 } 73 74 func (r *Repl) Eval(expr string) (tok token.Token, dump []string, err error) { 75 r.lastDump = nil 76 tok = r.firstToken(expr) 77 err = r.eval(tok, expr) 78 return tok, r.lastDump, err 79 } 80 81 func (r *Repl) Source() string { 82 return r.source 83 } 84 85 func (r *Repl) buildSource(expr string, tok token.Token) string { 86 v0 := strings.Join(r.imports, "\n") 87 v1 := strings.Join(r.globals, "\n") 88 v2 := strings.Join(r.infuncs, "\n") 89 switch tok { 90 case token.IMPORT: 91 v0 += "\n" + expr 92 case token.CONST, token.VAR, token.TYPE, token.FUNC: 93 v1 += "\n" + expr 94 default: 95 v2 += "\n" + expr 96 } 97 return fmt.Sprintf(`package main 98 %v 99 func __gossa_repl_used__(v interface{}){} 100 func __gossa_repl_dump__(v ...interface{}){} 101 %v 102 func main() { 103 %v 104 } 105 `, v0, v1, v2) 106 } 107 108 func (r *Repl) eval(tok token.Token, expr string) (err error) { 109 var src string 110 var inMain bool 111 switch tok { 112 case token.PACKAGE: 113 // skip package 114 return nil 115 case token.IMPORT: 116 src = r.buildSource(expr, tok) 117 errors, err := r.check(r.fileName, src) 118 if err != nil { 119 return err 120 } 121 if errors != nil { 122 return errors[0] 123 } 124 r.imports = append(r.imports, expr) 125 return nil 126 case token.CONST, token.FUNC, token.TYPE, token.VAR: 127 src = r.buildSource(expr, tok) 128 errors, err := r.check(r.fileName, src) 129 if err != nil { 130 return err 131 } 132 if errors != nil { 133 return errors[0] 134 } 135 default: 136 inMain = true 137 src = r.buildSource(expr, tok) 138 errors, err := r.check(r.fileName, src) 139 if err != nil { 140 return err 141 } 142 var fixed []string 143 for _, err := range errors { 144 e, ok := err.(types.Error) 145 if !ok { 146 return e 147 } 148 if strings.HasSuffix(e.Msg, errDeclNotUsed) { 149 v := e.Msg[0 : len(e.Msg)-len(errDeclNotUsed)-1] 150 fixed = append(fixed, "__gossa_repl_used__(&"+v+")") 151 fixed = append(fixed, "__gossa_repl_dump__("+v+")") 152 } else if strings.HasSuffix(e.Msg, errIsNotUsed) { 153 expr = "__gossa_repl_dump__(" + expr + ")" 154 } else { 155 return e 156 } 157 } 158 if len(fixed) != 0 { 159 expr += "\n" + strings.Join(fixed, "\n") 160 } 161 src = r.buildSource(expr, tok) 162 } 163 r.pkg, err = r.ctx.LoadFile(r.fset, r.fileName, src) 164 if err != nil { 165 return err 166 } 167 err = r.run() 168 if err == nil { 169 if inMain { 170 r.infuncs = append(r.infuncs, expr) 171 } else { 172 r.globals = append(r.globals, expr) 173 } 174 r.source = src 175 } 176 return err 177 } 178 179 const ( 180 errDeclNotUsed = "declared but not used" 181 errIsNotUsed = "is not used" 182 ) 183 184 func (r *Repl) check(filename string, src interface{}) (errors []error, err error) { 185 file, err := r.ctx.ParseFile(r.fset, filename, src) 186 if err != nil { 187 return nil, err 188 } 189 tc := &types.Config{ 190 Importer: NewImporter(r.ctx.Loader, r.ctx.External), 191 Sizes: r.ctx.Sizes, 192 DisableUnusedImportCheck: true, 193 } 194 tc.Error = func(err error) { 195 errors = append(errors, err) 196 } 197 pkg := types.NewPackage(file.Name.Name, "") 198 types.NewChecker(tc, r.fset, pkg, &types.Info{}).Files([]*ast.File{file}) 199 return 200 } 201 202 func (r *Repl) firstToken(src string) token.Token { 203 var s scanner.Scanner 204 file := r.fset.AddFile("", r.fset.Base(), len(src)) 205 s.Init(file, []byte(src), nil, 0) 206 _, tok, _ := s.Scan() 207 return tok 208 } 209 210 func (repl *Repl) run() error { 211 i, err := newInterp(repl.ctx, repl.pkg, repl.globalMap) 212 if err != nil { 213 return err 214 } 215 rinit, err := repl.runFunc(i, "init", repl.fsInit) 216 if err == nil { 217 repl.fsInit = rinit 218 repl.fsInit.pc-- 219 } 220 rmain, err := repl.runFunc(i, "main", repl.fsMain) 221 if err != nil { 222 return err 223 } 224 repl.fsMain = rmain 225 for k, v := range i.globals { 226 repl.globalMap[k.String()] = v 227 } 228 return nil 229 } 230 231 type fnState struct { 232 fr *frame 233 pc int 234 } 235 236 func (r *Repl) runFunc(i *Interp, fnname string, fs *fnState) (rfs *fnState, err error) { 237 defer func() { 238 r := recover() 239 if r != nil { 240 err = fmt.Errorf("%v", r) 241 } 242 }() 243 fn := r.pkg.Func(fnname) 244 if fn == nil { 245 return nil, fmt.Errorf("no function %v", fnname) 246 } 247 pfn := i.funcs[fn] 248 fr := pfn.allocFrame(nil) 249 if fs != nil { 250 copy(fr.stack, fs.fr.stack) 251 fr.pc = fs.pc 252 } 253 var pc int 254 for fr.pc != -1 { 255 fn := fr.pfn.Instrs[fr.pc] 256 pc = fr.pc 257 fr.pc++ 258 fn(fr) 259 } 260 return &fnState{fr: fr, pc: pc}, nil 261 }