github.com/goplus/igop@v0.25.0/repl/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 repl 18 19 import ( 20 "fmt" 21 "go/token" 22 "os/exec" 23 "reflect" 24 "regexp" 25 "strings" 26 27 "github.com/goplus/igop" 28 "github.com/goplus/igop/constant" 29 "golang.org/x/tools/go/ssa" 30 ) 31 32 const ( 33 // ContinuePrompt - the current code statement is not completed. 34 ContinuePrompt string = "... " 35 // NormalPrompt - start of a code statement. 36 NormalPrompt string = ">>> " 37 ) 38 39 type UI interface { 40 SetPrompt(prompt string) 41 Printf(format string, a ...interface{}) 42 } 43 44 type REPL struct { 45 *igop.Repl 46 term UI 47 more string 48 } 49 50 func NewREPL(mode igop.Mode) *REPL { 51 r := &REPL{} 52 igop.RegisterCustomBuiltin("__igop_repl_info__", func(v interface{}) { 53 r.Printf("%v %T\n", v, v) 54 }) 55 ctx := igop.NewContext(mode) 56 r.Repl = igop.NewRepl(ctx) 57 return r 58 } 59 60 func (r *REPL) SetUI(term UI) { 61 r.term = term 62 term.SetPrompt(NormalPrompt) 63 } 64 65 func (r *REPL) SetNormal() { 66 r.more = "" 67 r.SetPrompt(NormalPrompt) 68 } 69 70 func (r *REPL) SetPrompt(prompt string) { 71 if r.term != nil { 72 r.term.SetPrompt(prompt) 73 } 74 } 75 76 func (r *REPL) IsNormal() bool { 77 return r.more == "" 78 } 79 80 func (r *REPL) TryDump(expr string) bool { 81 i := r.Interp() 82 if i != nil { 83 if m, v, ok := i.GetSymbol(expr); ok { 84 switch p := m.(type) { 85 case *ssa.NamedConst: 86 r.Printf("const %v %v\n", constant.ExactConstant(p.Value.Value), p.Type()) 87 case *ssa.Global: 88 e := reflect.ValueOf(v).Elem().Interface() 89 r.Printf("%v %T (global var)\n", e, e) 90 case *ssa.Type: 91 if m.Package().Pkg.Name() == "main" { 92 return false 93 } 94 if r.tryDumpByPkg(expr) != nil { 95 r.Printf("%v %v\n", p.Type().Underlying(), v) 96 } 97 case *ssa.Function: 98 n := p.Signature.Params().Len() 99 if n == 0 || (n == 1 && p.Signature.Variadic()) { 100 return false 101 } 102 r.Printf("%v %v\n", v, p.Type()) 103 } 104 return true 105 } 106 } 107 return false 108 } 109 110 func (r *REPL) Dump(expr string) { 111 i := r.Interp() 112 if i != nil { 113 if m, v, ok := i.GetSymbol(expr); ok { 114 switch p := m.(type) { 115 case *ssa.NamedConst: 116 r.Printf("const %v %v\n", constant.ExactConstant(p.Value.Value), p.Type()) 117 case *ssa.Global: 118 e := reflect.ValueOf(v).Elem().Interface() 119 r.Printf("%v %T (global var)\n", e, e) 120 case *ssa.Type: 121 if r.tryDumpByPkg(expr) != nil { 122 r.Printf("%v %v\n", p.Type().Underlying(), v) 123 } 124 case *ssa.Function: 125 r.Printf("%v %v\n", v, p.Type()) 126 } 127 return 128 } 129 } 130 _, _, err := r.Eval(fmt.Sprintf("__igop_repl_info__(%v)", expr)) 131 if err == nil { 132 return 133 } 134 if err = r.godoc(expr); err == nil { 135 return 136 } 137 if err = r.tryDumpByPkg(expr); err == nil { 138 return 139 } 140 r.Printf("not found %v\n", expr) 141 } 142 143 func (r *REPL) tryDumpByPkg(expr string) error { 144 pkg, sym, _, err := parseSymbol(expr) 145 if err != nil { 146 return err 147 } 148 if pkgPath, found := findPkg(pkg); found { 149 if p, found := igop.LookupPackage(pkgPath); found { 150 if sym == "" { 151 r.Printf("%v\n", dumpPkg(p)) 152 return nil 153 } 154 if info, ok := lookupSymbol(p, sym); ok { 155 r.Printf("%v\n", info) 156 return nil 157 } 158 return fmt.Errorf("not found symbol %v.%v", pkg, sym) 159 } 160 } 161 return fmt.Errorf("not found pkg %v", pkg) 162 } 163 164 func (r *REPL) godoc(expr string) error { 165 gobin, err := exec.LookPath("go") 166 if err != nil { 167 return err 168 } 169 cmd := exec.Command(gobin, "doc", expr) 170 data, err := cmd.CombinedOutput() 171 if err != nil { 172 return err 173 } 174 r.Printf("%v\n", string(data)) 175 return nil 176 } 177 178 func (r *REPL) Printf(_fmt string, a ...interface{}) { 179 if r.term != nil { 180 r.term.Printf(_fmt, a...) 181 } else { 182 fmt.Printf(_fmt, a...) 183 } 184 } 185 186 var ( 187 regWord = regexp.MustCompile("\\w+") 188 ) 189 190 func (r *REPL) Run(line string) error { 191 var expr string 192 if r.more != "" { 193 if line == "" { 194 r.SetNormal() 195 return nil 196 } 197 expr = r.more + "\n" + line 198 } else { 199 if strings.HasPrefix(line, "?") { 200 r.Dump(strings.TrimSpace(line[1:])) 201 return nil 202 } else if regWord.MatchString(line) { 203 if r.TryDump(line) { 204 return nil 205 } 206 } 207 expr = line 208 } 209 tok, eval, err := r.Eval(expr) 210 if err != nil { 211 if checkMore(tok, err) { 212 r.more += "\n" + line 213 r.SetPrompt(ContinuePrompt) 214 return nil 215 } else { 216 r.SetNormal() 217 } 218 return err 219 } 220 switch len(eval) { 221 case 0: 222 case 1: 223 r.Printf("%v\n", eval[0]) 224 default: 225 var info []string 226 for _, v := range eval { 227 info = append(info, v.String()) 228 } 229 r.Printf("(%v)\n", strings.Join(info, ", ")) 230 } 231 r.SetNormal() 232 return nil 233 } 234 235 func checkMore(tok token.Token, err error) bool { 236 s := err.Error() 237 if strings.Contains(s, `expected `) { 238 return true 239 } 240 return false 241 }