diff --git a/cmd/internal/repl/repl.go b/cmd/internal/repl/repl.go index 926521e3..c87d5a48 100644 --- a/cmd/internal/repl/repl.go +++ b/cmd/internal/repl/repl.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "runtime" + "strings" "github.com/goplus/igop" "github.com/goplus/igop/cmd/internal/base" @@ -70,6 +71,12 @@ func runCmd(cmd *base.Command, args []string) { // state.SetCtrlCAborts(true) state.SetMultiLineMode(true) + state.SetCompleter(func(line string) []string { + if strings.TrimSpace(line) == "" { + return []string{line + " "} + } + return nil + }) ui := &LinerUI{state: state} var mode igop.Mode if flagDumpInstr { @@ -78,7 +85,11 @@ func runCmd(cmd *base.Command, args []string) { if flagTrace { mode |= igop.EnableTracing } - r := repl.NewREPL(mode) + var r *repl.REPL + igop.RegisterCustomBuiltin("exit", func() { + r.Interp().Exit(0) + }) + r = repl.NewREPL(mode) r.SetUI(ui) if supportGoplus && flagGoplus { r.SetFileName("main.gop") @@ -96,6 +107,15 @@ func runCmd(cmd *base.Command, args []string) { if line != "" { state.AppendHistory(line) } - r.Run(line) + err = r.Run(line) + switch e := err.(type) { + case nil: + // + case igop.ExitError: + fmt.Printf("exit %v\n", int(e)) + return + default: + fmt.Printf("error: %v\n", err) + } } } diff --git a/errors.go b/errors.go index 18fd9052..2d7dc2fb 100644 --- a/errors.go +++ b/errors.go @@ -1,6 +1,9 @@ package igop -import "errors" +import ( + "errors" + "fmt" +) var ( ErrNoPackage = errors.New("no package") @@ -13,3 +16,9 @@ var ( ErrNoFunction = errors.New("no function") ErrNoCustomBuiltin = errors.New("no custom builtin") ) + +type ExitError int + +func (r ExitError) Error() string { + return fmt.Sprintf("exit %v", int(r)) +} diff --git a/go.mod b/go.mod index b4bd119b..be24586d 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,12 @@ module github.com/goplus/igop go 1.14 require ( - github.com/goplus/gop v1.1.0 - github.com/goplus/gox v1.11.8 + github.com/goplus/gop v1.1.2 + github.com/goplus/gox v1.11.12 github.com/goplus/reflectx v0.8.10 github.com/peterh/liner v1.2.2 github.com/petermattis/goid v0.0.0-20220331194723-8ee3e6ded87a github.com/visualfc/funcval v0.1.3 github.com/visualfc/xtype v0.1.0 - golang.org/x/tools v0.1.10 + golang.org/x/tools v0.1.11 ) diff --git a/go.sum b/go.sum index 577d5444..8c5121fc 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,14 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/goplus/c2go v0.7.6/go.mod h1:NedKEtme4+2tPEK3IEtACPvUPm/Bq9+myFVcbxQK8yg= -github.com/goplus/gop v1.1.0 h1:DbIk7KGfp6KCBflcXxOfweNfWZWZljCEezuIUigvqjA= -github.com/goplus/gop v1.1.0/go.mod h1:mmGmU1rFKCuy658hmht6Lxj9vgb/fJTyHBwq/2AvMCE= -github.com/goplus/gox v1.11.8 h1:XVyhun8eq2gIAPpIKq+5MFoFqe+epALYUo00c66qH8g= -github.com/goplus/gox v1.11.8/go.mod h1:gu7fuQF8RmWPZUjd+tEJGuV3m/vOEv0bHrct0x/KatM= -github.com/goplus/libc v0.3.8/go.mod h1:5jwI9WTo0535XwnR5dFZuKgIXm2b2dOqulFon7C1yIE= -github.com/goplus/mod v0.9.10 h1:z7wrTT241dPSGJDeMs5uuqv/5CgXLomhOZGWEAZ+OpE= -github.com/goplus/mod v0.9.10/go.mod h1:NHU13OjeNV3ez1f+R9FLJIlIun0KNSuZb0jnmP3N3o8= +github.com/goplus/c2go v0.7.8/go.mod h1:6D9HpX+q/8/dmTT08l7sCpWyDTh22Sx3rJk83OvyVu8= +github.com/goplus/gop v1.1.2 h1:eUyYN5Ibn+GBEHkrAh80oM4OH6ePFSPGhE8R2E6R4BE= +github.com/goplus/gop v1.1.2/go.mod h1:RxPV2a2tM4gfAhdFRmELe/a2MW4XOQLf51CtufdVvbI= +github.com/goplus/gox v1.11.12 h1:zb+hNzKbUJ16tDyGlwDIHzdArRLeDz+wXaBsNXeSDew= +github.com/goplus/gox v1.11.12/go.mod h1:wRCRSNukie4cDqADF4w0Btc2Gk6V3p3V6hI5+rsVqa8= +github.com/goplus/libc v0.3.10/go.mod h1:gSQ8dVF/iKdoBn1ABeQfAF/ybaYX0pij3iPtC72FtJc= +github.com/goplus/mod v0.9.12 h1:CjgBGQIYqUTPGl3MrAS5CICzJwxbIfSa4OlEb141Gs4= +github.com/goplus/mod v0.9.12/go.mod h1:YoPIowz71rnLLROA4YG0AC8bzDtPRyMaQwgTRLr8ri4= github.com/goplus/reflectx v0.8.10 h1:lcB+wJiT8IxfSrhpqkLPZc5TWkT/m8xMzjHVDhyD6dU= github.com/goplus/reflectx v0.8.10/go.mod h1:XPDe5lYQ/8FN05bhqv6r1hhLxwmYIkZ5UvIkN1GNRYg= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -21,8 +21,8 @@ github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiN github.com/petermattis/goid v0.0.0-20220331194723-8ee3e6ded87a h1:VXRRto5GMJPNfB7MNbUVoFhtxwoYjBEsIt/NpWg42U0= github.com/petermattis/goid v0.0.0-20220331194723-8ee3e6ded87a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/qiniu/x v1.11.5 h1:TYr5cl4g2yoHAZeDK4MTjKF6CMoG+IHlCDvvM5qym6U= -github.com/qiniu/x v1.11.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs= +github.com/qiniu/x v1.11.9 h1:IfQNdeNcK43Q1+b/LdrcqmWjlhxq051YVBnua8J2qN8= +github.com/qiniu/x v1.11.9/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/visualfc/funcval v0.1.3 h1:hvqP5bkW2jeA4cf7QNM8bz+duJECFzQmSBmMJkkIt3A= @@ -32,7 +32,6 @@ github.com/visualfc/xtype v0.1.0/go.mod h1:VYIH9S2bmdWKlBb7c725ES6yKF9+pyHBU2SFN github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -54,9 +53,6 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY= +golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/interp.go b/interp.go index 4a27bee4..1b2bf544 100644 --- a/interp.go +++ b/interp.go @@ -51,12 +51,13 @@ import ( "go/types" "reflect" "runtime" + "strings" "sync" "sync/atomic" "unsafe" - "github.com/visualfc/xtype" "github.com/petermattis/goid" + "github.com/visualfc/xtype" "golang.org/x/tools/go/ssa" ) @@ -113,6 +114,10 @@ type Interp struct { exited int32 // is call os.Exit } +func (i *Interp) MainPkg() *ssa.Package { + return i.mainpkg +} + func (i *Interp) installed(path string) (pkg *Package, ok bool) { pkg, ok = i.loader.Installed(path) return @@ -175,7 +180,7 @@ func (i *Interp) FindMethod(mtyp reflect.Type, fn *types.Func) func([]reflect.Va panic(fmt.Sprintf("Not found method %v", fn)) } -func (i *Interp) makeFunc(typ reflect.Type, pfn *function, env []value) reflect.Value { +func (i *Interp) makeFunction(typ reflect.Type, pfn *function, env []value) reflect.Value { return reflect.MakeFunc(typ, func(args []reflect.Value) []reflect.Value { return i.callFunctionByReflect(i.tryDeferFrame(), typ, pfn, args, env) }) @@ -1048,7 +1053,7 @@ func (i *Interp) GetFunc(key string) (interface{}, bool) { if !ok { return nil, false } - return i.makeFunc(i.toType(fn.Type()), i.funcs[fn], nil).Interface(), true + return i.makeFunction(i.toType(fn.Type()), i.funcs[fn], nil).Interface(), true } func (i *Interp) GetVarAddr(key string) (interface{}, bool) { @@ -1088,6 +1093,53 @@ func (i *Interp) GetType(key string) (reflect.Type, bool) { return i.toType(t.Type()), true } +func (i *Interp) GetSymbol(key string) (m ssa.Member, v interface{}, ok bool) { + ar := strings.Split(key, ".") + var pkg *ssa.Package + switch len(ar) { + case 1: + pkg = i.mainpkg + case 2: + pkgs := i.prog.AllPackages() + for _, p := range pkgs { + if p.Pkg.Path() == ar[0] || p.Pkg.Name() == ar[0] { + pkg = p + break + } + } + if pkg == nil { + return + } + key = ar[1] + default: + return + } + m, ok = pkg.Members[key] + if !ok { + return + } + switch p := m.(type) { + case *ssa.NamedConst: + v = p.Value.Value + case *ssa.Global: + v = i.globals[p] + case *ssa.Function: + typ := i.toType(p.Type()) + v = i.makeFunction(typ, i.funcs[p], nil) + case *ssa.Type: + v = i.toType(p.Type()) + } + return +} + +func (i *Interp) Exit(code int) { + if i != nil && atomic.LoadInt32(&i.goexited) == 1 { + i.chexit <- code + } else { + panic(exitPanic(code)) + } +} + // deref returns a pointer's element type; otherwise it returns typ. // TODO(adonovan): Import from ssa? func deref(typ types.Type) types.Type { diff --git a/opblock.go b/opblock.go index c3938cb2..ebb5944b 100644 --- a/opblock.go +++ b/opblock.go @@ -192,7 +192,7 @@ func (p *function) regInstr(v ssa.Value) uint32 { if v.Blocks != nil { typ := p.Interp.preToType(v.Type()) pfn := p.Interp.loadFunction(v) - vs = p.Interp.makeFunc(typ, pfn, nil).Interface() + vs = p.Interp.makeFunction(typ, pfn, nil).Interface() } else { ext, ok := findExternFunc(p.Interp, v) if !ok { @@ -407,7 +407,7 @@ func makeInstr(interp *Interp, pfn *function, instr ssa.Instruction) func(fr *fr for i, _ := range instr.Bindings { bindings = append(bindings, fr.reg(ib[i])) } - v := interp.makeFunc(typ, pfn, bindings) + v := interp.makeFunction(typ, pfn, bindings) fr.setReg(ir, v.Interface()) } case *ssa.MakeChan: diff --git a/package.go b/package.go index 8c1dd165..eac2f785 100644 --- a/package.go +++ b/package.go @@ -3,12 +3,22 @@ package igop import ( "go/constant" "reflect" + "sort" ) var ( registerPkgs = make(map[string]*Package) ) +// PackageList return all register packages +func PackageList() (list []string) { + for pkg, _ := range registerPkgs { + list = append(list, pkg) + } + sort.Strings(list) + return +} + // LookupPackage lookup register pkgs func LookupPackage(name string) (pkg *Package, ok bool) { pkg, ok = registerPkgs[name] diff --git a/repl.go b/repl.go index 517bb8df..0092c7aa 100644 --- a/repl.go +++ b/repl.go @@ -6,7 +6,7 @@ import ( "go/scanner" "go/token" "go/types" - "os" + "strconv" "strings" "golang.org/x/tools/go/ssa" @@ -23,26 +23,33 @@ func main(){} */ type Repl struct { - ctx *Context - fset *token.FileSet - pkg *ssa.Package - frame *frame // last frame - pc int // last pc - fsInit *fnState // func init - fsMain *fnState // func main - imports []string // import lines - globals []string // global var/func/type - infuncs []string // in main func - source string // all source - lastDump []string // last __igop_repl_dump__ - globalMap map[string]interface{} - lastInterp *Interp - fileName string + ctx *Context + fset *token.FileSet + pkg *ssa.Package + builtin *ast.File + interp *Interp // last interp + frame *frame // last frame + fsInit *fnState // func init + fsMain *fnState // func main + imports []string // import lines + globals []string // global var/func/type + infuncs []string // in main func + lastDump []string // last __igop_repl_dump__ + source string // all source + fileName string + globalMap map[string]interface{} } func toDump(i []interface{}) (dump []string) { for _, v := range i { - dump = append(dump, fmt.Sprintf("%v", v)) + dump = append(dump, fmt.Sprintf("%v %T", v, v)) + } + return +} + +func toResultDump(vs []interface{}, rs *types.Tuple) (dump []string) { + for i, v := range vs { + dump = append(dump, fmt.Sprintf("%v %v", v, rs.At(i).Type())) } return } @@ -55,15 +62,25 @@ func NewRepl(ctx *Context) *Repl { } ctx.evalMode = true ctx.evalInit = make(map[string]bool) - - ctx.SetOverrideFunction("main.__igop_repl_dump__", func(v ...interface{}) { + RegisterCustomBuiltin("__igop_repl_used__", func(v interface{}) {}) + RegisterCustomBuiltin("__igop_repl_dump__", func(v ...interface{}) { r.lastDump = toDump(v) }) ctx.evalCallFn = func(call *ssa.Call, res ...interface{}) { - if strings.HasPrefix(call.Call.Value.Name(), "__igop_repl_") { + if len(*call.Referrers()) != 0 { + return + } + v := call.Call.Value + if un, ok := v.(*ssa.UnOp); ok { + v = un.X + } + if strings.Contains(v.Name(), "__igop_repl_") { return } - r.lastDump = toDump(res) + r.lastDump = toResultDump(res, call.Call.Signature().Results()) + } + if f, err := ParseBuiltin(r.fset, "main"); err == nil { + r.builtin = f } return r } @@ -79,6 +96,10 @@ func (r *Repl) Eval(expr string) (tok token.Token, dump []string, err error) { return tok, r.lastDump, err } +func (r *Repl) Interp() *Interp { + return r.interp +} + func (r *Repl) Source() string { return r.source } @@ -97,8 +118,6 @@ func (r *Repl) buildSource(expr string, tok token.Token) string { } return fmt.Sprintf(`package main %v -func __igop_repl_used__(v interface{}){} -func __igop_repl_dump__(v ...interface{}){} %v func main() { %v @@ -149,8 +168,12 @@ func (r *Repl) eval(tok token.Token, expr string) (err error) { if strings.HasSuffix(e.Msg, errDeclNotUsed) { v := e.Msg[0 : len(e.Msg)-len(errDeclNotUsed)-1] fixed = append(fixed, "__igop_repl_used__(&"+v+")") - fixed = append(fixed, "__igop_repl_dump__("+v+")") + // fixed = append(fixed, "__igop_repl_dump__("+v+")") } else if strings.HasSuffix(e.Msg, errIsNotUsed) { + if c, ok := extractConstant([]byte(e.Msg[:len(e.Msg)-len(errIsNotUsed)])); ok { + r.lastDump = []string{c.Lit} + return nil + } expr = "__igop_repl_dump__(" + expr + ")" } else { return e @@ -196,7 +219,13 @@ func (r *Repl) check(filename string, src interface{}) (errors []error, err erro errors = append(errors, err) } pkg := types.NewPackage(file.Name.Name, "") - types.NewChecker(tc, r.fset, pkg, &types.Info{}).Files([]*ast.File{file}) + var files []*ast.File + if r.builtin != nil { + files = []*ast.File{r.builtin, file} + } else { + files = []*ast.File{file} + } + types.NewChecker(tc, r.fset, pkg, &types.Info{}).Files(files) return } @@ -223,6 +252,7 @@ func (repl *Repl) run() error { return err } repl.fsMain = rmain + repl.interp = i for k, v := range i.globals { repl.globalMap[k.String()] = v } @@ -239,7 +269,7 @@ func (r *Repl) runFunc(i *Interp, fnname string, fs *fnState) (rfs *fnState, err switch p := recover().(type) { case nil: case exitPanic: - os.Exit(int(p)) + err = ExitError(int(p)) default: err = fmt.Errorf("%v", r) } @@ -263,3 +293,75 @@ func (r *Repl) runFunc(i *Interp, fnname string, fs *fnState) (rfs *fnState, err } return &fnState{fr: fr, pc: pc}, nil } + +type tokenLit struct { + Pos token.Pos + Tok token.Token + Lit string +} + +// extractConstant extract constant int and overflows float/complex +func extractConstant(src []byte) (constant *tokenLit, ok bool) { + var s scanner.Scanner + fset := token.NewFileSet() + file := fset.AddFile("", fset.Base(), len(src)) + s.Init(file, src, nil, 0) + pos, tok, lit := s.Scan() + if tok == token.EOF { + return + } + first := &tokenLit{pos, tok, lit} + var check int + for { + _, tok, lit := s.Scan() + if tok == token.EOF { + break + } + switch tok { + case token.LPAREN: + check++ + case token.RPAREN: + check-- + case token.IDENT: + if check == 1 && lit == "constant" { + var nodes []*tokenLit + loop: + for { + pos, tok, lit := s.Scan() + if tok == token.EOF { + break + } + switch tok { + case token.LPAREN: + check++ + case token.RPAREN: + check-- + if check == 0 { + break loop + } + default: + nodes = append(nodes, &tokenLit{pos, tok, lit}) + } + } + switch len(nodes) { + case 0: + return first, true + case 1: + switch nodes[0].Tok { + case token.INT: + return nodes[0], true + case token.FLOAT: + // extract if not parse float64 + if _, err := strconv.ParseFloat(nodes[0].Lit, 128); err != nil { + return nodes[0], true + } + } + default: + last := nodes[len(nodes)-1] + return &tokenLit{nodes[0].Pos, token.IMAG, string(src[int(nodes[0].Pos)-1 : int(last.Pos)+len(last.Lit)])}, true + } + } + } + } + return +} diff --git a/repl/pkg.go b/repl/pkg.go new file mode 100644 index 00000000..9f046803 --- /dev/null +++ b/repl/pkg.go @@ -0,0 +1,282 @@ +package repl + +import ( + "bytes" + "errors" + "fmt" + "io" + "reflect" + "sort" + "strings" + + "github.com/goplus/igop" +) + +// parseSymbol breaks str apart into a pkg, symbol and method +// fmt.Println => fmt Println +// fmt.Stringer.String => fmt Stringer.String +func parseSymbol(str string) (pkg, symbol string, method string, err error) { + if str == "" { + return + } + elem := strings.Split(str, ".") + switch len(elem) { + case 1: + case 2: + symbol = elem[1] + case 3: + symbol = elem[1] + method = elem[2] + default: + err = errors.New("too many periods in symbol specification") + return + } + pkg = elem[0] + return +} + +func findPkg(name string) (pkg string, found bool) { + list := igop.PackageList() + for _, v := range list { + if name == v { + return v, true + } + } + for _, v := range list { + if strings.HasSuffix(v, "/"+name) { + return v, true + } + } + return +} + +func lookupSymbol(p *igop.Package, sym string) (info string, found bool) { + if v, ok := p.UntypedConsts[sym]; ok { + return fmt.Sprintf("const %v.%v %v = %v", p.Name, sym, v.Typ, v.Value), true + } + if v, ok := p.TypedConsts[sym]; ok { + return fmt.Sprintf("const %v.%v %v = %v", p.Name, sym, v.Typ, v.Value), true + } + if v, ok := p.Vars[sym]; ok { + return fmt.Sprintf("var %v.%v %v", p.Name, sym, v.Type().Elem()), true + } + if v, ok := p.Funcs[sym]; ok { + var buf bytes.Buffer + writeFunc(&buf, p.Name+"."+sym, v.Type()) + return buf.String(), true + } + if t, ok := p.NamedTypes[sym]; ok { + var buf bytes.Buffer + if t.Typ.Kind() == reflect.Struct { + writeStruct(&buf, p.Name+"."+sym, t.Typ) + buf.WriteByte('\n') + } else { + fmt.Fprintf(&buf, "type %v.%v %v\n", p.Name, sym, t.Typ.Kind()) + } + for _, name := range strings.Split(t.Methods, ",") { + if m, ok := t.Typ.MethodByName(name); ok { + writeMethod(&buf, m) + buf.WriteByte('\n') + } + } + ptyp := reflect.PtrTo(t.Typ) + for _, name := range strings.Split(t.PtrMethods, ",") { + if m, ok := ptyp.MethodByName(name); ok { + writeMethod(&buf, m) + buf.WriteByte('\n') + } + } + return buf.String(), true + } + if t, ok := p.Interfaces[sym]; ok { + var buf bytes.Buffer + writeInterface(&buf, p.Name+"."+sym, t) + buf.WriteByte('\n') + return buf.String(), true + } + return +} + +func dumpPkg(p *igop.Package) string { + var buf bytes.Buffer + fmt.Fprintf(&buf, "package %v // import %q\n\n", p.Name, p.Path) + // untyped const + var uconst []string + for v := range p.UntypedConsts { + uconst = append(uconst, v) + } + sort.Strings(uconst) + for _, v := range uconst { + t := p.UntypedConsts[v] + fmt.Fprintf(&buf, "const %v = %v\n", v, t.Value) + } + // typed const + var tconst []string + for v := range p.TypedConsts { + tconst = append(tconst, v) + } + sort.Strings(tconst) + for _, v := range tconst { + t := p.TypedConsts[v] + fmt.Fprintf(&buf, "const %v %v = %v\n", v, t.Typ, t.Value) + } + // var + var vars []string + for v := range p.Vars { + vars = append(vars, v) + } + sort.Strings(vars) + for _, v := range vars { + t := p.Vars[v] + fmt.Fprintf(&buf, "var %v %v\n", v, t.Elem().Type()) + } + // funcs + var funcs []string + for v := range p.Funcs { + funcs = append(funcs, v) + } + sort.Strings(funcs) + for _, v := range funcs { + f := p.Funcs[v] + writeFunc(&buf, v, f.Type()) + buf.WriteByte('\n') + } + // named types + var types []string + for v := range p.NamedTypes { + types = append(types, v) + } + sort.Strings(types) + for _, v := range types { + t := p.NamedTypes[v] + fmt.Fprintf(&buf, "type %v %v\n", v, t.Typ.Kind()) + for _, name := range strings.Split(t.Methods, ",") { + if m, ok := t.Typ.MethodByName(name); ok { + buf.WriteString(" ") + writeMethod(&buf, m) + buf.WriteByte('\n') + } + } + ptyp := reflect.PtrTo(t.Typ) + for _, name := range strings.Split(t.PtrMethods, ",") { + if m, ok := ptyp.MethodByName(name); ok { + buf.WriteString(" ") + writeMethod(&buf, m) + buf.WriteByte('\n') + } + } + } + // interface + var ifaces []string + for v := range p.Interfaces { + ifaces = append(ifaces, v) + } + sort.Strings(ifaces) + for _, v := range ifaces { + t := p.Interfaces[v] + fmt.Fprintf(&buf, "type %v interface\n", v) + n := t.NumMethod() + for i := 0; i < n; i++ { + m := t.Method(i) + buf.WriteString(" ") + writeFunc(&buf, m.Name, m.Type) + buf.WriteByte('\n') + } + } + return buf.String() +} + +func writeInterface(w io.Writer, name string, typ reflect.Type) { + n := typ.NumMethod() + if n == 0 { + fmt.Fprintf(w, "type %v interface{}", name) + return + } + fmt.Fprintf(w, "type %v interface{\n", name) + for i := 0; i < n; i++ { + m := typ.Method(i) + w.Write([]byte(" ")) + writeFunc(w, m.Name, m.Type) + w.Write([]byte{'\n'}) + } + w.Write([]byte{'}'}) +} + +func writeStruct(w io.Writer, name string, typ reflect.Type) { + n := typ.NumField() + if n == 0 { + fmt.Fprintf(w, "type %v struct{}", name) + return + } + fmt.Fprintf(w, "type %v struct{\n", name) + for i := 0; i < n; i++ { + f := typ.Field(i) + if f.PkgPath != "" { + fmt.Fprintf(w, " // ... other fields elided ...\n") + break + } + if f.Anonymous { + fmt.Fprintf(w, " %v\n", f.Type) + } else { + fmt.Fprintf(w, " %v %v\n", f.Name, f.Type) + } + } + fmt.Fprintf(w, "}") +} + +func writeMethod(w io.Writer, m reflect.Method) { + typ := m.Type + numIn := typ.NumIn() + numOut := typ.NumOut() + var ins []string + if typ.IsVariadic() { + for i := 1; i < numIn-1; i++ { + ins = append(ins, typ.In(i).String()) + } + ins = append(ins, "..."+typ.In(numIn-1).Elem().String()) + } else { + for i := 1; i < numIn; i++ { + ins = append(ins, typ.In(i).String()) + } + } + switch numOut { + case 0: + fmt.Fprintf(w, "func (%v) %v(%v)", typ.In(0), m.Name, strings.Join(ins, ", ")) + case 1: + fmt.Fprintf(w, "func (%v) %v(%v) %v", typ.In(0), m.Name, strings.Join(ins, ", "), typ.Out(0).String()) + default: + var outs []string + for i := 0; i < numOut; i++ { + outs = append(outs, typ.Out(i).String()) + } + fmt.Fprintf(w, "func (%v) %v(%v) (%v)", typ.In(0), m.Name, strings.Join(ins, ", "), strings.Join(outs, ", ")) + } +} + +func writeFunc(w io.Writer, name string, typ reflect.Type) { + numIn := typ.NumIn() + numOut := typ.NumOut() + var ins []string + if typ.IsVariadic() { + for i := 0; i < numIn-1; i++ { + ins = append(ins, typ.In(i).String()) + } + ins = append(ins, "..."+typ.In(numIn-1).Elem().String()) + } else { + for i := 0; i < numIn; i++ { + ins = append(ins, typ.In(i).String()) + } + } + switch numOut { + case 0: + fmt.Fprintf(w, "func %v(%v)", name, strings.Join(ins, ", ")) + case 1: + fmt.Fprintf(w, "func %v(%v) %v", name, strings.Join(ins, ", "), typ.Out(0).String()) + default: + var outs []string + for i := 0; i < numOut; i++ { + outs = append(outs, typ.Out(i).String()) + } + fmt.Fprintf(w, "func %v(%v) (%v)", name, strings.Join(ins, ", "), strings.Join(outs, ", ")) + } +} diff --git a/repl/repl.go b/repl/repl.go index 05937581..46576195 100644 --- a/repl/repl.go +++ b/repl/repl.go @@ -1,10 +1,16 @@ package repl import ( + "fmt" "go/token" + "go/types" + "os/exec" + "reflect" + "regexp" "strings" "github.com/goplus/igop" + "golang.org/x/tools/go/ssa" ) const ( @@ -20,17 +26,19 @@ type UI interface { } type REPL struct { - repl *igop.Repl + *igop.Repl term UI more string } func NewREPL(mode igop.Mode) *REPL { + r := &REPL{} + igop.RegisterCustomBuiltin("__igop_repl_info__", func(v interface{}) { + r.Printf("%v %v\n", v, reflect.TypeOf(v)) + }) ctx := igop.NewContext(mode) - repl := igop.NewRepl(ctx) - return &REPL{ - repl: repl, - } + r.Repl = igop.NewRepl(ctx) + return r } func (r *REPL) SetUI(term UI) { @@ -40,43 +48,159 @@ func (r *REPL) SetUI(term UI) { func (r *REPL) SetNormal() { r.more = "" - r.term.SetPrompt(NormalPrompt) + r.SetPrompt(NormalPrompt) +} + +func (r *REPL) SetPrompt(prompt string) { + if r.term != nil { + r.term.SetPrompt(prompt) + } } func (r *REPL) IsNormal() bool { return r.more == "" } -func (r *REPL) SetFileName(filename string) { - r.repl.SetFileName(filename) +func (r *REPL) TryDump(expr string) bool { + i := r.Interp() + if i != nil { + if m, v, ok := i.GetSymbol(expr); ok { + switch p := m.(type) { + case *ssa.NamedConst: + r.Printf("%v %v (const)\n", v, p.Type()) + case *ssa.Global: + r.Printf("%v %v (global var)\n", reflect.ValueOf(v).Elem().Interface(), p.Type().(*types.Pointer).Elem()) + case *ssa.Type: + if m.Package().Pkg.Name() == "main" { + return false + } + r.Printf("%v %v\n", p.Type().Underlying(), v) + case *ssa.Function: + n := p.Signature.Params().Len() + if n == 0 || (n == 1 && p.Signature.Variadic()) { + return false + } + r.Printf("%v %v\n", v, p.Type()) + } + return true + } + } + return false } -func (r *REPL) Run(line string) { +func (r *REPL) Dump(expr string) { + i := r.Interp() + if i != nil { + if m, v, ok := i.GetSymbol(expr); ok { + switch p := m.(type) { + case *ssa.NamedConst: + r.Printf("%v %v (const)\n", v, p.Type()) + case *ssa.Global: + r.Printf("%v %v (global var)\n", reflect.ValueOf(v).Elem().Interface(), p.Type().(*types.Pointer).Elem()) + case *ssa.Type: + r.Printf("%v %v\n", p.Type().Underlying(), v) + case *ssa.Function: + r.Printf("%v %v\n", v, p.Type()) + } + return + } + } + _, _, err := r.Eval(fmt.Sprintf("__igop_repl_info__(%v)", expr)) + if err != nil { + r.tryPkg(expr) + return + if err := r.godoc(expr); err != nil { + r.Printf("not found %v\n", expr) + } + } +} + +func (r *REPL) tryPkg(expr string) error { + pkg, sym, _, err := parseSymbol(expr) + if err != nil { + return err + } + if pkgPath, found := findPkg(pkg); found { + if p, found := igop.LookupPackage(pkgPath); found { + if sym == "" { + r.Printf("%v\n", dumpPkg(p)) + return nil + } + if info, ok := lookupSymbol(p, sym); ok { + r.Printf("%v\n", info) + return nil + } + return fmt.Errorf("not found symbol %v.%v", pkg, sym) + } + } + return fmt.Errorf("not found pkg %v", pkg) +} + +func (r *REPL) godoc(expr string) error { + gobin, err := exec.LookPath("go") + if err != nil { + return err + } + cmd := exec.Command(gobin, "doc", expr) + data, err := cmd.CombinedOutput() + if err != nil { + return err + } + r.Printf("%v\n", string(data)) + return nil +} + +func (r *REPL) Printf(_fmt string, a ...interface{}) { + if r.term != nil { + r.term.Printf(_fmt, a...) + } else { + fmt.Printf(_fmt, a...) + } +} + +var ( + regWord = regexp.MustCompile("\\w+") +) + +func (r *REPL) Run(line string) error { var expr string if r.more != "" { if line == "" { r.SetNormal() - return + return nil } expr = r.more + "\n" + line } else { + if strings.HasPrefix(line, "?") { + r.Dump(strings.TrimSpace(line[1:])) + return nil + } else if regWord.MatchString(line) { + if r.TryDump(line) { + return nil + } + } expr = line } - tok, dump, err := r.repl.Eval(expr) + tok, dump, err := r.Eval(expr) if err != nil { if checkMore(tok, err) { r.more += "\n" + line - r.term.SetPrompt(ContinuePrompt) + r.SetPrompt(ContinuePrompt) + return nil } else { r.SetNormal() - r.term.Printf("error: %v\n", err) } - return + return err } - if len(dump) > 0 { - r.term.Printf(": %v\n", strings.Join(dump, " ")) + switch len(dump) { + case 0: + case 1: + r.Printf("%v\n", dump[0]) + default: + r.Printf("(%v)\n", strings.Join(dump, ", ")) } r.SetNormal() + return nil } func checkMore(tok token.Token, err error) bool { diff --git a/repl_test.go b/repl_test.go index 8ea36725..3d51b7c0 100644 --- a/repl_test.go +++ b/repl_test.go @@ -18,9 +18,9 @@ func TestReplExpr(t *testing.T) { `a+b`, } result := []string{ - `[1]`, - `[2]`, - `[3]`, + `[]`, + `[]`, + `[3 int]`, } for i, expr := range list { _, v, err := repl.Eval(expr) @@ -44,11 +44,11 @@ func TestReplImports(t *testing.T) { `c`, } result := []string{ - `[1]`, - `[2]`, `[]`, - `[1-2]`, - `[1-2]`, + `[]`, + `[]`, + `[]`, + `[1-2 string]`, } for i, expr := range list { _, v, err := repl.Eval(expr) @@ -72,11 +72,11 @@ func TestReplClosure(t *testing.T) { `d`, } result := []string{ - `[1]`, - `[2]`, - `-`, - `[3]`, - `[3]`, + `[]`, + `[]`, + `[]`, + `[]`, + `[3 int]`, } for i, expr := range list { _, v, err := repl.Eval(expr) @@ -105,13 +105,13 @@ func TestReplVar(t *testing.T) { `c`, } result := []string{ - `[1]`, - `[2]`, - `-`, - `-`, - `[103]`, - `[103]`, - `[100]`, + `[]`, + `[]`, + `[]`, + `[]`, + `[]`, + `[103 int]`, + `[100 int]`, } for i, expr := range list { _, v, err := repl.Eval(expr) @@ -137,21 +137,21 @@ func TestReplType(t *testing.T) { }`, `v1 := &T{10,20}`, `import "fmt"`, - `r1 := fmt.Sprint(v1)`, + `v1`, `func (t *T) String() string { return fmt.Sprintf("%v-%v",t.X,t.Y) }`, `v2 := &T{10,20}`, - `r2 := fmt.Sprint(v2)`, + `v2`, } result := []string{ `-`, - `[&{10 20}]`, `-`, - `[&{10 20}]`, `-`, - `[10-20]`, - `[10-20]`, + `[&{10 20} *main.T]`, + `-`, + `-`, + `[10-20 *main.T]`, } for i, expr := range list { _, v, err := repl.Eval(expr) @@ -176,15 +176,17 @@ func TestReplFunc(t *testing.T) { `a`, `fmt.Println(a)`, `s := fmt.Sprint(a)`, + `s`, `fmt.Println(s)`, } result := []string{ - `[hello]`, + `[]`, + `-`, + `[hello string]`, + `[6 int error]`, `-`, - `[hello]`, - `[6 ]`, - `[hello]`, - `[6 ]`, + `[hello string]`, + `[6 int error]`, } for i, expr := range list { _, v, err := repl.Eval(expr) @@ -250,8 +252,8 @@ func TestReplInit(t *testing.T) { `-`, `-`, `-`, - `[11]`, - `[21]`, + `[11 int]`, + `[21 int]`, } for i, expr := range list { _, v, err := repl.Eval(expr)