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  }