github.com/goplus/igop@v0.17.0/repl.go (about) 1 /* 2 * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package igop 18 19 import ( 20 "fmt" 21 "go/ast" 22 "go/constant" 23 "go/scanner" 24 "go/token" 25 "go/types" 26 "runtime" 27 "strings" 28 29 xconst "github.com/goplus/igop/constant" 30 "golang.org/x/tools/go/ssa" 31 ) 32 33 /* 34 package main 35 36 import "fmt" 37 38 const/var/type/func 39 40 func main(){} 41 */ 42 43 type Repl struct { 44 ctx *Context // interp context 45 pkg *ssa.Package // main package 46 builtin *ast.File // builtin func 47 interp *Interp // last interp 48 fsInit *fnState // func init 49 fsMain *fnState // func main 50 globalMap map[string]interface{} // keep repl global map 51 fileName string // file.go or file.gop 52 source string // all source 53 imports []string // import lines 54 globals []string // global var/func/type 55 infuncs []string // in main func 56 lastDump []string // last __igop_repl_dump__ 57 } 58 59 func toDump(i []interface{}) (dump []string) { 60 for _, v := range i { 61 dump = append(dump, fmt.Sprintf("%v %T", v, v)) 62 } 63 return 64 } 65 66 func toResultDump(interp *Interp, vs []interface{}, rs *types.Tuple) (dump []string) { 67 for i, v := range vs { 68 dump = append(dump, fmt.Sprintf("%v %v", v, interp.toType(rs.At(i).Type()))) 69 } 70 return 71 } 72 73 func NewRepl(ctx *Context) *Repl { 74 r := &Repl{ 75 ctx: ctx, 76 globalMap: make(map[string]interface{}), 77 } 78 ctx.SetEvalMode(true) 79 RegisterCustomBuiltin("__igop_repl_used__", func(v interface{}) {}) 80 RegisterCustomBuiltin("__igop_repl_dump__", func(v ...interface{}) { 81 r.lastDump = toDump(v) 82 }) 83 // reset runtime.GC to default 84 ctx.RegisterExternal("runtime.GC", runtime.GC) 85 ctx.evalCallFn = func(interp *Interp, call *ssa.Call, res ...interface{}) { 86 if len(*call.Referrers()) != 0 { 87 return 88 } 89 v := call.Call.Value 90 if un, ok := v.(*ssa.UnOp); ok { 91 v = un.X 92 } 93 if strings.Contains(v.Name(), "__igop_repl_") { 94 return 95 } 96 r.lastDump = toResultDump(interp, res, call.Call.Signature().Results()) 97 } 98 if f, err := ParseBuiltin(r.ctx.FileSet, "main"); err == nil { 99 r.builtin = f 100 } 101 return r 102 } 103 104 func (r *Repl) SetFileName(filename string) { 105 r.fileName = filename 106 } 107 108 func (r *Repl) Eval(expr string) (tok token.Token, dump []string, err error) { 109 r.lastDump = nil 110 tok = r.firstToken(expr) 111 err = r.eval(tok, expr) 112 return tok, r.lastDump, err 113 } 114 115 func (r *Repl) Interp() *Interp { 116 return r.interp 117 } 118 119 func (r *Repl) Source() string { 120 return r.source 121 } 122 123 func (r *Repl) buildSource(expr string, tok token.Token) string { 124 v0 := strings.Join(r.imports, "\n") 125 v1 := strings.Join(r.globals, "\n") 126 v2 := strings.Join(r.infuncs, "\n") 127 switch tok { 128 case token.IMPORT: 129 v0 += "\n" + expr 130 case token.CONST, token.VAR, token.TYPE, token.FUNC: 131 v1 += "\n" + expr 132 default: 133 v2 += "\n" + expr 134 } 135 return fmt.Sprintf(`package main 136 %v 137 %v 138 func main() { 139 %v 140 } 141 `, v0, v1, v2) 142 } 143 144 func (r *Repl) eval(tok token.Token, expr string) (err error) { 145 var src string 146 var inMain bool 147 var evalConst bool 148 switch tok { 149 case token.PACKAGE: 150 // skip package 151 return nil 152 case token.IMPORT: 153 src = r.buildSource(expr, tok) 154 errors, err := r.check(r.fileName, src) 155 if err != nil { 156 return err 157 } 158 if errors != nil { 159 return errors[0] 160 } 161 r.imports = append(r.imports, expr) 162 return nil 163 case token.FUNC: 164 src = r.buildSource(expr, tok) 165 errors, err := r.check(r.fileName, src) 166 if err != nil { 167 // check funlit 168 msg := err.Error() 169 if strings.Contains(msg, errMaybeGoFunLit) || strings.Contains(msg, errMaybeGopFunLit) { 170 inMain = true 171 src = r.buildSource(expr, token.ILLEGAL) 172 errors, err = r.check(r.fileName, src) 173 } 174 if err != nil { 175 return err 176 } 177 } 178 if errors != nil { 179 return errors[0] 180 } 181 case token.CONST, token.TYPE, token.VAR: 182 src = r.buildSource(expr, tok) 183 errors, err := r.check(r.fileName, src) 184 if err != nil { 185 return err 186 } 187 if errors != nil { 188 return errors[0] 189 } 190 default: 191 switch tok { 192 case token.FOR, token.IF, token.SWITCH, token.SELECT: 193 expr = "func(){\n" + expr + "\n}()" 194 } 195 inMain = true 196 src = r.buildSource(expr, tok) 197 errors, err := r.check(r.fileName, src) 198 if err != nil { 199 return err 200 } 201 var fixed []string 202 for _, err := range errors { 203 e, ok := err.(types.Error) 204 if !ok { 205 return e 206 } 207 if strings.HasSuffix(e.Msg, errDeclaredNotUsed) { 208 v := e.Msg[0 : len(e.Msg)-len(errDeclaredNotUsed)-1] 209 fixed = append(fixed, "__igop_repl_used__(&"+v+")") 210 // fixed = append(fixed, "__igop_repl_dump__("+v+")") 211 } else if strings.HasSuffix(e.Msg, errIsNotUsed) { 212 if _, ok := extractConstant([]byte(e.Msg[:len(e.Msg)-len(errIsNotUsed)])); ok { 213 // r.lastDump = []string{c.Lit} 214 // return nil 215 expr = "const __igop_repl_const__ = " + expr 216 tok = token.CONST 217 evalConst = true 218 } else { 219 expr = "__igop_repl_dump__(" + expr + ")" 220 } 221 } else { 222 return e 223 } 224 } 225 if len(fixed) != 0 { 226 expr += "\n" + strings.Join(fixed, "\n") 227 } 228 src = r.buildSource(expr, tok) 229 } 230 r.pkg, err = r.ctx.LoadFile(r.fileName, src) 231 if err != nil { 232 return err 233 } 234 i, err := newInterp(r.ctx, r.pkg, r.globalMap) 235 if err != nil { 236 return err 237 } 238 rinit, err := r.runFunc(i, "init", r.fsInit) 239 if err == nil { 240 rinit.pc-- 241 } 242 rmain, err := r.runFunc(i, "main", r.fsMain) 243 if err != nil { 244 return err 245 } 246 if evalConst { 247 m := i.mainpkg.Members["__igop_repl_const__"] 248 c, _ := m.(*ssa.NamedConst) 249 s, _ := xconst.ExactConstantEx(c.Value.Value, true) 250 kind := c.Value.Value.Kind() 251 if kind == constant.Float { 252 es := c.Value.Value.ExactString() 253 r.lastDump = []string{fmt.Sprintf("%v %v\n(%v)", s, c.Type(), es)} 254 } else { 255 r.lastDump = []string{fmt.Sprintf("%v %v", s, c.Type())} 256 } 257 return nil 258 } 259 r.interp = i 260 r.fsInit = rinit 261 r.fsMain = rmain 262 for k, v := range i.globals { 263 r.globalMap[k] = v 264 } 265 if inMain { 266 r.infuncs = append(r.infuncs, expr) 267 } else { 268 r.globals = append(r.globals, expr) 269 } 270 r.source = src 271 return nil 272 } 273 274 const ( 275 errIsNotUsed = "is not used" 276 errMaybeGoFunLit = `expected 'IDENT', found '{'` 277 errMaybeGopFunLit = `expected '(',` 278 ) 279 280 func (r *Repl) check(filename string, src interface{}) (errors []error, err error) { 281 file, err := r.ctx.ParseFile(filename, src) 282 if err != nil { 283 return nil, err 284 } 285 tc := &types.Config{ 286 Importer: NewImporter(r.ctx), 287 DisableUnusedImportCheck: true, 288 } 289 tc.Error = func(err error) { 290 errors = append(errors, err) 291 } 292 pkg := types.NewPackage(file.Name.Name, "") 293 var files []*ast.File 294 if r.builtin != nil { 295 files = []*ast.File{r.builtin, file} 296 } else { 297 files = []*ast.File{file} 298 } 299 types.NewChecker(tc, r.ctx.FileSet, pkg, &types.Info{}).Files(files) 300 return 301 } 302 303 func (r *Repl) firstToken(src string) token.Token { 304 var s scanner.Scanner 305 file := r.ctx.FileSet.AddFile("", -1, len(src)) 306 s.Init(file, []byte(src), nil, 0) 307 _, tok, _ := s.Scan() 308 return tok 309 } 310 311 type fnState struct { 312 fr *frame 313 pc int 314 } 315 316 func (r *Repl) runFunc(i *Interp, fnname string, fs *fnState) (rfs *fnState, err error) { 317 defer func() { 318 switch p := recover().(type) { 319 case nil: 320 case exitPanic: 321 err = ExitError(int(p)) 322 default: 323 err = fmt.Errorf("%v", p) 324 } 325 }() 326 fn := r.pkg.Func(fnname) 327 if fn == nil { 328 return nil, fmt.Errorf("no function %v", fnname) 329 } 330 pfn := i.funcs[fn] 331 fr := pfn.allocFrame(&frame{}) 332 if fs != nil { 333 copy(fr.stack, fs.fr.stack) 334 fr.ipc = fs.pc 335 } 336 var pc int 337 for fr.ipc != -1 { 338 fn := fr.pfn.Instrs[fr.ipc] 339 pc = fr.ipc 340 fr.ipc++ 341 fn(fr) 342 } 343 return &fnState{fr: fr, pc: pc}, nil 344 } 345 346 type tokenLit struct { 347 Lit string 348 Pos token.Pos 349 Tok token.Token 350 } 351 352 // extractConstant extract constant int and overflows float/complex 353 func extractConstant(src []byte) (constant *tokenLit, ok bool) { 354 var s scanner.Scanner 355 fset := token.NewFileSet() 356 file := fset.AddFile("", fset.Base(), len(src)) 357 s.Init(file, src, nil, 0) 358 pos, tok, lit := s.Scan() 359 if tok == token.EOF { 360 return 361 } 362 first := &tokenLit{lit, pos, tok} 363 var check int 364 for { 365 _, tok, lit := s.Scan() 366 if tok == token.EOF { 367 break 368 } 369 switch tok { 370 case token.LPAREN: 371 check++ 372 case token.RPAREN: 373 check-- 374 case token.IDENT: 375 if check == 1 && lit == "constant" { 376 var nodes []*tokenLit 377 loop: 378 for { 379 pos, tok, lit := s.Scan() 380 if tok == token.EOF { 381 break 382 } 383 switch tok { 384 case token.LPAREN: 385 check++ 386 case token.RPAREN: 387 check-- 388 if check == 0 { 389 break loop 390 } 391 default: 392 nodes = append(nodes, &tokenLit{lit, pos, tok}) 393 } 394 } 395 switch len(nodes) { 396 case 0: 397 return first, true 398 case 1: 399 switch nodes[0].Tok { 400 case token.INT: 401 return nodes[0], true 402 case token.FLOAT: 403 return nodes[0], true 404 // extract if not parse float64 405 // if _, err := strconv.ParseFloat(nodes[0].Lit, 128); err != nil { 406 // return nodes[0], true 407 // } 408 } 409 default: 410 last := nodes[len(nodes)-1] 411 return &tokenLit{string(src[int(nodes[0].Pos)-1 : int(last.Pos)+len(last.Lit)]), nodes[0].Pos, token.IMAG}, true 412 } 413 } 414 } 415 } 416 return 417 }