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  }