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