├── .gitattributes ├── .gitignore ├── COPYING ├── README.md ├── complete.go ├── liner.go └── main.go /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | gop -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2020, 353474225@qq.com 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [中文 README](#中文) 2 | 3 | 4 | # [gop](http://github.com/simplejia/gop) (go REPL) 5 | ## Original Intention 6 | * Sometimes when we want to verify a go function quickly, coding in a file is too inefficient. While we have gop, opening a shell environment immediately, it will save the context automatically and enable you to import or export snippet at any time. In addition, it also can complete the code automatically and so on. 7 | 8 | ## Features 9 | * history record: when gop is started, it will generate .gop folder under your home directory where inputting history is recorded. 10 | * tab complete: when you tap tab, it can complete package and function which needs [gocode](http://github.com/nsf/gocode), if you have already installed gocode in your server but it can not work well, please run`go get -u github.com/nsf/gocode` to update it and install it again. 11 | * It enables you to view code in real time and edit code [! command] 12 | * snippet: import and export template [<,> command] 13 | 14 | ## Installation 15 | > go get -u github.com/simplejia/gop 16 | 17 | ## Notice: 18 | * When you input code, it supports continued line 19 | * For the following code, the whole messages will output together when the executing is over 20 | > print(1);time.Sleep(time.Second);print(2) 21 | 22 | * You can input `echo 123`, echo is alias of println,You can redefine println variable to use your own print method, for example, defining println as the following: (utils.IprintD is characterized by printing the actual content of a pointer, even if the pointer is nested, fmt.printf can not do that) 23 | ``` 24 | import "github.com/simplejia/utils" 25 | var println = utils.IprintD 26 | ``` 27 | * When you import project package, you had better install package file to pkg directory in advance via go install which can accelerate the executing. 28 | * You can import package in advance and atomically import it in subsequent use 29 | * When gop is started, it will automatically import template code such as $PWD/gop.tmpl or $HOME/.gop/gop.tmpl, you can save your frequently-used code to gop.tmpl 30 | 31 | ## demo 32 | ``` 33 | $ gop 34 | Welcome to the Go Partner! [[version: 1.7, created by simplejia] 35 | Enter '?' for a list of commands. 36 | GOP$ ? 37 | Commands: 38 | ?|help help menu 39 | -[dpc][#],[#]-[#],... pop last/specific (declaration|package|code) 40 | ![!] inspect source [with linenum] 41 | tmpl write tmpl 43 | [#](...) add def or code 44 | reset reset 45 | list tmpl list 46 | arg set or get command-line argument 47 | GOP$ for i:=1; i<3; i++ { 48 | ..... print(i) 49 | ..... time.Sleep(time.Millisecond) 50 | .....} 51 | 1 52 | 2 53 | GOP$ import _ "github.com/simplejia/wsp/demo/mysql" 54 | GOP$ import _ "github.com/simplejia/wsp/demo/redis" 55 | GOP$ import _ "github.com/simplejia/wsp/demo/conf" 56 | GOP$ import "github.com/simplejia/lc" 57 | GOP$ import "github.com/simplejia/wsp/demo/service" 58 | GOP$ lc.Init(1024) 59 | GOP$ demoService := service.NewDemo() 60 | GOP$ demoService.Set("123", "456") 61 | GOP$ time.Sleep(time.Millisecond) 62 | GOP$ echo demoService.Get("123") 63 | 456 64 | GOP$ >gop 65 | GOP$ 命令功能] 105 | 106 | ## 安装 107 | > go get -u github.com/simplejia/gop 108 | 109 | ## 注意: 110 | * 输入代码时,支持续行 111 | * 对于如下代码,只会在执行结束后一并输出 112 | > print(1);time.Sleep(time.Second);print(2) 113 | 114 | * 可以通过echo 123这种方式输出, echo是println的简写,你甚至可以重新定义println变量来使用自己的打印方法,比如像我这样定义(utils.IprintD的特点是可以打印出指针指向的实际内容,就算是嵌套的指针也可以,fmt.Printf做不到): 115 | ``` 116 | import "github.com/simplejia/utils" 117 | var println = utils.IprintD 118 | ``` 119 | * 导入项目package时,最好提前通过go install方式安装包文件到pkg目录,这样可以加快执行速度 120 | * 可以提前import包,后续使用时再自动引入 121 | * gop启动后会自动导入$PWD/gop.tmpl或者$HOME/.gop/gop.tmpl模板代码,可以把常用的代码保存到gop.tmpl里 122 | 123 | ## demo 124 | ``` 125 | $ gop 126 | Welcome to the Go Partner! [[version: 1.7, created by simplejia] 127 | Enter '?' for a list of commands. 128 | GOP$ ? 129 | Commands: 130 | ?|help help menu 131 | -[dpc][#],[#]-[#],... pop last/specific (declaration|package|code) 132 | ![!] inspect source [with linenum] 133 | tmpl write tmpl 135 | [#](...) add def or code 136 | reset reset 137 | list tmpl list 138 | arg set or get command-line argument 139 | GOP$ for i:=1; i<3; i++ { 140 | ..... print(i) 141 | ..... time.Sleep(time.Millisecond) 142 | .....} 143 | 1 144 | 2 145 | GOP$ import _ "github.com/simplejia/wsp/demo/mysql" 146 | GOP$ import _ "github.com/simplejia/wsp/demo/redis" 147 | GOP$ import _ "github.com/simplejia/wsp/demo/conf" 148 | GOP$ import "github.com/simplejia/lc" 149 | GOP$ import "github.com/simplejia/wsp/demo/service" 150 | GOP$ lc.Init(1024) 151 | GOP$ demoService := service.NewDemo() 152 | GOP$ demoService.Set("123", "456") 153 | GOP$ time.Sleep(time.Millisecond) 154 | GOP$ echo demoService.Get("123") 155 | 456 156 | GOP$ >gop 157 | GOP$ i { 124 | return line[:i+1], completeImport(line[i+1 : pos]), line[pos:] 125 | } 126 | } 127 | } 128 | 129 | for _, cmdPrefix := range []string{"<", ">"} { 130 | if strings.HasPrefix(line, cmdPrefix) { 131 | i := strings.Index(line, cmdPrefix) 132 | if i != -1 { 133 | if pos > i { 134 | return line[:i+1], completeTmpl(line[i+1 : pos]), line[pos:] 135 | } 136 | } 137 | } 138 | } 139 | 140 | if pos == 0 || unicode.IsSpace(rune(line[pos-1])) { 141 | return "", nil, "" 142 | } 143 | 144 | if gocode.Available() == false { 145 | return "", nil, "" 146 | } 147 | 148 | oldPos := pos 149 | pos, cands, err := completeCode(w.source(false, false, true), line, pos) 150 | if err != nil { 151 | return "", nil, "" 152 | } 153 | 154 | return line[0:pos], cands, line[oldPos:] 155 | } 156 | -------------------------------------------------------------------------------- /liner.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "regexp" 8 | 9 | "strings" 10 | "text/scanner" 11 | 12 | "github.com/peterh/liner" 13 | ) 14 | 15 | const ( 16 | promptContinue = "....." 17 | indent = " " 18 | ) 19 | 20 | type contLiner struct { 21 | *liner.State 22 | buffer string 23 | depth int 24 | } 25 | 26 | func newContLiner() *contLiner { 27 | rl := liner.NewLiner() 28 | rl.SetCtrlCAborts(true) 29 | return &contLiner{State: rl} 30 | } 31 | 32 | func (cl *contLiner) promptString(p string) string { 33 | if cl.buffer != "" { 34 | return promptContinue + strings.Repeat(indent, cl.depth) 35 | } 36 | 37 | return p 38 | } 39 | 40 | func (cl *contLiner) Prompt(p string) (string, error) { 41 | line, err := cl.State.Prompt(cl.promptString(p)) 42 | switch err { 43 | case io.EOF: 44 | println() 45 | case liner.ErrPromptAborted: 46 | if cl.buffer != "" { 47 | cl.Accepted() 48 | } else { 49 | println("(^D to quit)") 50 | } 51 | err = nil 52 | //case liner.ErrPromptCtrlZ: 53 | // go exec.Command("kill", "-SIGTSTP", strconv.Itoa(os.Getpid())).Run() 54 | // err = nil 55 | case nil: 56 | if cl.buffer != "" { 57 | cl.buffer += "\n" + line 58 | } else { 59 | cl.buffer = line 60 | } 61 | } 62 | 63 | return cl.buffer, err 64 | } 65 | 66 | func (cl *contLiner) Accepted() { 67 | cl.buffer = regexp.MustCompile(`\n+`).ReplaceAllString(cl.buffer, "\n") 68 | 69 | if p := strings.Index(cl.buffer, "\n"); p != -1 { 70 | cl.buffer = cl.buffer[:p] + cl.buffer[p+1:] 71 | } 72 | if p := strings.LastIndex(cl.buffer, "\n"); p != -1 { 73 | cl.buffer = cl.buffer[:p] + cl.buffer[p+1:] 74 | } 75 | cl.State.AppendHistory(strings.Replace(cl.buffer, "\n", ";", -1)) 76 | cl.buffer = "" 77 | } 78 | 79 | func (cl *contLiner) Reindent() { 80 | oldDepth := cl.depth 81 | cl.depth = cl.countDepth() 82 | 83 | if cl.depth < oldDepth { 84 | lines := strings.Split(cl.buffer, "\n") 85 | if len(lines) > 1 { 86 | lastLine := lines[len(lines)-1] 87 | fmt.Print("\x1b[1A") // Cursor up one 88 | fmt.Printf("\r%s%s", cl.promptString(""), lastLine) 89 | fmt.Print("\x1b[0K") // Erase to right 90 | fmt.Print("\n") 91 | } 92 | } 93 | } 94 | 95 | func (cl *contLiner) countDepth() int { 96 | depth := 0 97 | 98 | s := new(scanner.Scanner) 99 | s.Init(bytes.NewBufferString(cl.buffer)) 100 | tok := s.Scan() 101 | for tok != scanner.EOF { 102 | switch tok { 103 | case '{', '(': 104 | depth++ 105 | case '}', ')': 106 | depth-- 107 | } 108 | tok = s.Scan() 109 | } 110 | 111 | if depth < 0 { 112 | depth = 0 113 | } 114 | return depth 115 | } 116 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // A REPL tool for golang. 2 | // Created by simplejia [8/2015] 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | "errors" 8 | "fmt" 9 | "go/ast" 10 | "go/parser" 11 | "go/printer" 12 | "go/scanner" 13 | "go/token" 14 | "io" 15 | "io/ioutil" 16 | "os" 17 | "os/exec" 18 | "os/signal" 19 | "path/filepath" 20 | "reflect" 21 | "regexp" 22 | "runtime" 23 | "strconv" 24 | "strings" 25 | "syscall" 26 | "unicode" 27 | ) 28 | 29 | var ( 30 | home = filepath.Join(os.Getenv("HOME"), ".gop") 31 | ) 32 | 33 | // Workspace is the main struct for gop 34 | type Workspace struct { 35 | pkgs []interface{} 36 | pkgsNotimport []interface{} 37 | defs []interface{} 38 | codes []interface{} 39 | files *token.FileSet 40 | args string 41 | } 42 | 43 | func (w *Workspace) source(printDpc, printLinenums, printNotimport bool) string { 44 | source := "" 45 | if printDpc { 46 | source += "\t" 47 | } 48 | source += "package main\n\n" 49 | 50 | pkgsNum := 0 51 | for _, v := range w.pkgs { 52 | str := new(bytes.Buffer) 53 | printer.Fprint(str, w.files, v) 54 | 55 | if printDpc { 56 | source += "p" + strconv.Itoa(pkgsNum) + ":\t" 57 | } 58 | source += str.String() + "\n" 59 | pkgsNum++ 60 | } 61 | 62 | if printNotimport { 63 | for _, v := range w.pkgsNotimport { 64 | str := new(bytes.Buffer) 65 | printer.Fprint(str, w.files, v) 66 | 67 | if printDpc { 68 | source += "p" + strconv.Itoa(pkgsNum) + ":\t" 69 | } 70 | source += str.String() + " // imported and not used\n" 71 | pkgsNum++ 72 | } 73 | } 74 | 75 | source += "\n" 76 | 77 | for pos, v := range w.defs { 78 | str := new(bytes.Buffer) 79 | printer.Fprint(str, w.files, v) 80 | 81 | if printDpc { 82 | source += "d" + strconv.Itoa(pos) + ":\t" 83 | source += strings.Join(strings.Split(str.String(), "\n"), "\n\t") 84 | } else { 85 | source += str.String() 86 | } 87 | source += "\n\n" 88 | } 89 | 90 | if printDpc { 91 | source += "\t" 92 | } 93 | source += "func main() {\n" 94 | 95 | for pos, v := range w.codes { 96 | str := new(bytes.Buffer) 97 | printer.Fprint(str, w.files, v) 98 | 99 | if printDpc { 100 | source += "c" + strconv.Itoa(pos) + ":\t" 101 | source += "\t" + strings.Join(strings.Split(str.String(), "\n"), "\n\t\t") 102 | } else { 103 | source += "\t" + strings.Join(strings.Split(str.String(), "\n"), "\n\t") 104 | } 105 | source += "\n" 106 | } 107 | 108 | if printDpc { 109 | source += "\t" 110 | } 111 | source += "}\n" 112 | 113 | if printLinenums { 114 | newsource := "" 115 | for line, item := range strings.Split(source, "\n") { 116 | newsource += strconv.Itoa(line+1) + "\t" + item + "\n" 117 | } 118 | source = newsource 119 | } 120 | 121 | return source 122 | } 123 | 124 | func compile(w *Workspace) (err error) { 125 | file := filepath.Join(home, "gop.go") 126 | ioutil.WriteFile(file, []byte(w.source(false, false, false)), 0644) 127 | 128 | out := "" 129 | if runtime.GOOS == "windows" { 130 | out = "gop.exe" 131 | } else { 132 | out = "gop" 133 | } 134 | out = filepath.Join(home, out) 135 | 136 | args := []string{} 137 | args = append(args, "build") 138 | args = append(args, "-o", out, file) 139 | stdoutStderr, err := exec.Command("go", args...).CombinedOutput() 140 | if err != nil { 141 | if len(stdoutStderr) > 0 { 142 | err = fmt.Errorf("%s", stdoutStderr) 143 | } 144 | return 145 | } 146 | 147 | return 148 | } 149 | 150 | func run(w *Workspace) (hasOutput bool, err error) { 151 | file := filepath.Join(home, "gop") 152 | matchs := regexp.MustCompile(`-?\w+|".*?[^\\"]"`).FindAllString(w.args, -1) 153 | for n, match := range matchs { 154 | matchs[n] = strings.Replace(strings.Trim(match, "\""), `\"`, `"`, -1) 155 | } 156 | 157 | outBuf := new(bytes.Buffer) 158 | errBuf := new(bytes.Buffer) 159 | 160 | cmd := exec.Command(file, matchs...) 161 | cmdout, err := cmd.StdoutPipe() 162 | if err != nil { 163 | return 164 | } 165 | cmderr, err := cmd.StderrPipe() 166 | if err != nil { 167 | return 168 | } 169 | 170 | stdout := io.MultiWriter(os.Stdout, outBuf) 171 | stderr := io.MultiWriter(os.Stderr, errBuf) 172 | 173 | err = cmd.Start() 174 | if err != nil { 175 | return 176 | } 177 | 178 | go func() { 179 | io.Copy(stdout, cmdout) 180 | }() 181 | go func() { 182 | io.Copy(stderr, cmderr) 183 | }() 184 | 185 | err = cmd.Wait() 186 | if err != nil { 187 | return 188 | } 189 | 190 | if outBuf.Len() > 0 || errBuf.Len() > 0 { 191 | hasOutput = true 192 | } 193 | 194 | return 195 | } 196 | 197 | func parseDeclList(fset *token.FileSet, filename string, src string) ([]ast.Decl, error) { 198 | pkg := "" 199 | if strings.Index(src, "package ") == -1 { 200 | pkg = "package p;" 201 | } 202 | f, err := parser.ParseFile(fset, filename, pkg+src, 0) 203 | if err != nil { 204 | return nil, err 205 | } 206 | return f.Decls, nil 207 | } 208 | 209 | func parseStmtList(fset *token.FileSet, filename string, src string) ([]ast.Stmt, error) { 210 | pkg := "" 211 | if strings.Index(src, "package ") == -1 { 212 | pkg = "package p;" 213 | } 214 | f, err := parser.ParseFile(fset, filename, pkg+"func _(){"+src+"}", 0) 215 | if err != nil { 216 | return nil, err 217 | } 218 | return f.Decls[0].(*ast.FuncDecl).Body.List, nil 219 | } 220 | 221 | func sourceDefaultDPC(w *Workspace) { 222 | for _, value := range []string{ 223 | "fmt", 224 | "strconv", 225 | "strings", 226 | "time", 227 | "encoding/json", 228 | "bytes", 229 | } { 230 | if func() bool { 231 | for _, pkg := range w.pkgs { 232 | v := pkg.(*ast.GenDecl).Specs[0].(*ast.ImportSpec) 233 | if v.Path.Value == "\""+value+"\"" && 234 | v.Name == nil { 235 | return true 236 | } 237 | } 238 | return false 239 | }() { 240 | continue 241 | } 242 | tree, _ := parseDeclList(w.files, "gop", "import \""+value+"\"") 243 | w.pkgsNotimport = append(w.pkgsNotimport, tree[0]) 244 | } 245 | } 246 | 247 | func execAlias(w *Workspace, line string) string { 248 | if line == "help" { 249 | return "?" 250 | } 251 | 252 | sps := []string{} 253 | for _, sp := range strings.Split(line, "\n") { 254 | if p := "echo "; strings.HasPrefix(sp, p) { 255 | sps = append(sps, "println("+sp[len(p):]+")") 256 | } else { 257 | sps = append(sps, sp) 258 | } 259 | } 260 | return strings.Join(sps, "\n") 261 | } 262 | 263 | func execSpecial(w *Workspace, line string) bool { 264 | if strings.HasPrefix(line, ">") { 265 | file := strings.TrimSpace(line[1:]) 266 | if file != "" { 267 | file = filepath.Join(home, file) 268 | if !strings.HasSuffix(file, ".tmpl") { 269 | file += ".tmpl" 270 | } 271 | ioutil.WriteFile(file, []byte(w.source(false, false, false)), 0644) 272 | } 273 | return true 274 | } 275 | if strings.HasPrefix(line, "<") && !strings.HasPrefix(line, "<-") { 276 | file := strings.TrimSpace(line[1:]) 277 | if file == "" { 278 | fmt.Println("No file specified for include.") 279 | return true 280 | } 281 | if !strings.HasSuffix(file, ".tmpl") { 282 | file += ".tmpl" 283 | } 284 | bs, err := ioutil.ReadFile(file) 285 | if err != nil { 286 | if os.IsNotExist(err) { 287 | bs, err = ioutil.ReadFile(filepath.Join(home, file)) 288 | } 289 | if err != nil { 290 | fmt.Println("ReadFile error:", err) 291 | return true 292 | } 293 | } 294 | 295 | sepBegin, sepEnd := "func main() {", "}" 296 | if pos := strings.Index(string(bs), sepBegin); pos != -1 { 297 | bs = append(bs[:pos], bs[pos+len(sepBegin):]...) 298 | if pos := strings.LastIndex(string(bs), sepEnd); pos != -1 { 299 | bs = append(bs[:pos], bs[pos+len(sepEnd):]...) 300 | } 301 | } 302 | 303 | w.pkgs = nil 304 | w.pkgsNotimport = nil 305 | w.codes = nil 306 | w.defs = nil 307 | tmpline := "" 308 | for _, line := range strings.Split(string(bs), "\n") { 309 | tmpline += line + "\n" 310 | notComplete, err := parseGo4import(w, tmpline) 311 | if err != nil { 312 | fmt.Println("ParseGo error:", err) 313 | break 314 | } 315 | if notComplete { 316 | continue 317 | } 318 | tmpline = "" 319 | } 320 | sourceDefaultDPC(w) 321 | return true 322 | } 323 | if line == "reset" { 324 | w.pkgs = nil 325 | w.pkgsNotimport = nil 326 | w.defs = nil 327 | w.codes = nil 328 | sourceDefaultDPC(w) 329 | return true 330 | } 331 | if line == "list" { 332 | entries, err := ioutil.ReadDir(home) 333 | if err != nil { 334 | fmt.Printf("ReadDir %s: %s\n", home, err) 335 | return true 336 | } 337 | 338 | tmpls := []string{} 339 | for _, fi := range entries { 340 | if fi.IsDir() { 341 | continue 342 | } 343 | 344 | name := fi.Name() 345 | if strings.HasPrefix(name, ".") || 346 | !strings.HasSuffix(name, ".tmpl") { 347 | continue 348 | } 349 | 350 | tmpls = append(tmpls, name) 351 | } 352 | for pos, tmpl := range tmpls { 353 | fmt.Printf("%d\t%s\n", pos, tmpl) 354 | } 355 | return true 356 | } 357 | if line == "arg" { 358 | fmt.Printf("%s\n", w.args) 359 | return true 360 | } 361 | if p := "arg "; strings.HasPrefix(line, p) && 362 | !strings.HasPrefix(line, p+"=") && 363 | !strings.HasPrefix(line, p+":=") { 364 | w.args = strings.TrimSpace(line[len(p):]) 365 | return true 366 | } 367 | return false 368 | } 369 | 370 | func removeByIndex(w *Workspace, cmdArgs string) { 371 | if len(cmdArgs) == 0 { 372 | fmt.Println("Error: no item specified for remove") 373 | return 374 | } 375 | 376 | itemType := cmdArgs[0] 377 | itemListLen := map[byte]int{ 378 | 'd': len(w.defs) + 1, 379 | 'p': len(w.pkgs) + len(w.pkgsNotimport) + 1, 380 | 'c': len(w.codes) + 1, 381 | }[itemType] - 1 382 | 383 | if itemListLen == -1 { 384 | fmt.Printf("Error: invalid item type '%c'\n", itemType) 385 | return 386 | } 387 | if itemListLen == 0 { 388 | fmt.Printf("Error: no more '%c' to remove\n", itemType) 389 | return 390 | } 391 | itemsToRemove := getIndices(itemListLen, cmdArgs[1:]) 392 | 393 | switch itemType { 394 | case 'd': 395 | removeSlice(&w.defs, itemsToRemove) 396 | case 'p': 397 | items4import, items4notimport := []bool{}, []bool{} 398 | for pos, v := range itemsToRemove { 399 | if pos < len(w.pkgs) { 400 | items4import = append(items4import, v) 401 | } else { 402 | items4notimport = append(items4notimport, v) 403 | } 404 | } 405 | removeSlice(&w.pkgs, items4import) 406 | removeSlice(&w.pkgsNotimport, items4notimport) 407 | case 'c': 408 | removeSlice(&w.codes, itemsToRemove) 409 | } 410 | } 411 | 412 | func getIndices(itemListLen int, cmdArgs string) []bool { 413 | itemsToRemove := make([]bool, itemListLen) 414 | 415 | cmdArgs = strings.TrimSpace(cmdArgs) 416 | if len(cmdArgs) == 0 { 417 | itemsToRemove[itemListLen-1] = true 418 | return itemsToRemove 419 | } 420 | 421 | itemIndices := []string{} 422 | for _, vi := range strings.Split(cmdArgs, ",") { 423 | if vj := strings.Split(vi, "-"); len(vj) == 2 { 424 | i, err := strconv.Atoi(vj[0]) 425 | if err != nil { 426 | fmt.Printf("Error: %s not integer\n", vj[0]) 427 | continue 428 | } 429 | j, err := strconv.Atoi(vj[1]) 430 | if err != nil { 431 | fmt.Printf("Error: %s not integer\n", vj[1]) 432 | continue 433 | } 434 | for k := i; k <= j; k++ { 435 | itemIndices = append(itemIndices, strconv.Itoa(k)) 436 | } 437 | } else { 438 | itemIndices = append(itemIndices, vi) 439 | } 440 | } 441 | 442 | for _, itemIndexStr := range itemIndices { 443 | if itemIndexStr == "" { 444 | continue 445 | } 446 | itemIndex, err := strconv.Atoi(itemIndexStr) 447 | if err != nil { 448 | fmt.Printf("Error: %s not integer\n", itemIndexStr) 449 | continue 450 | } 451 | if itemIndex < 0 || itemIndex >= itemListLen { 452 | fmt.Printf("Error: %d out of range\n", itemIndex) 453 | continue 454 | } 455 | itemsToRemove[itemIndex] = true 456 | } 457 | 458 | return itemsToRemove 459 | } 460 | 461 | func removeSlice(ps interface{}, removes []bool) { 462 | rps := reflect.Indirect(reflect.ValueOf(ps)) 463 | num := rps.Len() 464 | if num == 0 { 465 | return 466 | } 467 | 468 | rpsNew := reflect.MakeSlice(rps.Type(), 0, 0) 469 | for i := 0; i < num; i++ { 470 | if i < len(removes) && removes[i] { 471 | continue 472 | } 473 | rpsNew = reflect.Append(rpsNew, rps.Index(i)) 474 | } 475 | rps.Set(rpsNew) 476 | } 477 | 478 | func parseGo(w *Workspace, line string) (notComplete bool, err error) { 479 | pos := -1 480 | if unicode.IsDigit(rune(line[0])) { 481 | idx := strings.IndexFunc(line[1:], func(r rune) bool { return !unicode.IsDigit(r) }) 482 | if idx == -1 { 483 | return 484 | } 485 | idx++ 486 | pos, err = strconv.Atoi(line[:idx]) 487 | if err != nil { 488 | return 489 | } 490 | line = strings.TrimSpace(line[idx:]) 491 | } 492 | 493 | var tree interface{} 494 | tree, err = parseDeclList(w.files, "gop", line[0:]) 495 | if err != nil { 496 | tree, err = parseStmtList(w.files, "gop", line[0:]) 497 | if err != nil { 498 | if _, ok := err.(scanner.ErrorList); ok { 499 | err = nil 500 | notComplete = true 501 | } 502 | return 503 | } 504 | } 505 | 506 | bkupPkgs := append([]interface{}(nil), w.pkgs...) 507 | bkupPkgsNotimport := append([]interface{}(nil), w.pkgsNotimport...) 508 | bkupCodes := append([]interface{}(nil), w.codes...) 509 | bkupDefs := append([]interface{}(nil), w.defs...) 510 | bkupFiles := w.files 511 | 512 | var isCodeDefine bool 513 | 514 | switch v := tree.(type) { 515 | case []ast.Stmt: 516 | if pos > len(w.codes) || pos < 0 { 517 | pos = len(w.codes) 518 | } 519 | for i := len(v) - 1; i >= 0; i-- { 520 | if vI, ok := v[i].(*ast.AssignStmt); ok { 521 | if vI.Tok == token.DEFINE { 522 | for _, nameI := range vI.Lhs { 523 | strI := new(bytes.Buffer) 524 | printer.Fprint(strI, w.files, nameI) 525 | if strI.String() == "_" { 526 | continue 527 | } 528 | tree, _ := parseStmtList(w.files, "gop", "_ = "+strI.String()) 529 | w.codes = append(w.codes, nil) 530 | copy(w.codes[pos+1:], w.codes[pos:]) 531 | w.codes[pos] = tree[0] 532 | } 533 | } 534 | isCodeDefine = true 535 | } 536 | w.codes = append(w.codes, nil) 537 | copy(w.codes[pos+1:], w.codes[pos:]) 538 | w.codes[pos] = v[i] 539 | } 540 | case []ast.Decl: 541 | if pos > len(w.defs) || pos < 0 { 542 | pos = len(w.defs) 543 | } 544 | for i := len(v) - 1; i >= 0; i-- { 545 | if vI, ok := v[i].(*ast.GenDecl); ok { 546 | if vI.Tok == token.IMPORT { 547 | for _, spec := range vI.Specs { 548 | name := spec.(*ast.ImportSpec).Name.String() 549 | value := spec.(*ast.ImportSpec).Path.Value 550 | if func() bool { 551 | for _, pkg := range w.pkgs { 552 | vJ := pkg.(*ast.GenDecl).Specs[0].(*ast.ImportSpec) 553 | if vJ.Path.Value == value && 554 | vJ.Name.String() == name { 555 | return true 556 | } 557 | } 558 | return false 559 | }() { 560 | continue 561 | } 562 | if func() bool { 563 | for _, pkg := range w.pkgsNotimport { 564 | vJ := pkg.(*ast.GenDecl).Specs[0].(*ast.ImportSpec) 565 | if vJ.Path.Value == value && 566 | vJ.Name.String() == name { 567 | return true 568 | } 569 | } 570 | return false 571 | }() { 572 | continue 573 | } 574 | var tree []ast.Decl 575 | if spec.(*ast.ImportSpec).Name == nil { 576 | tree, _ = parseDeclList(w.files, "gop", "import "+value) 577 | } else { 578 | tree, _ = parseDeclList(w.files, "gop", "import "+name+" "+value) 579 | } 580 | w.pkgs = append(w.pkgs, tree[0]) 581 | isCodeDefine = true 582 | } 583 | continue 584 | } 585 | } 586 | 587 | w.defs = append(w.defs, nil) 588 | copy(w.defs[pos+1:], w.defs[pos:]) 589 | w.defs[pos] = v[i] 590 | isCodeDefine = true 591 | } 592 | default: 593 | err = errors.New("Fatal error: Unknown tree type.") 594 | return 595 | } 596 | 597 | var ( 598 | hasOutput bool 599 | matches [][]string 600 | ) 601 | 602 | err = compile(w) 603 | if err == nil { 604 | goto run 605 | } 606 | 607 | matches = regexp.MustCompile(`imported and not used: (".+?")( as (.+))?`).FindAllStringSubmatch(err.Error(), -1) 608 | if len(matches) == 0 { 609 | matches = regexp.MustCompile(`(".+?") imported( as (.+))? and not used`).FindAllStringSubmatch(err.Error(), -1) 610 | } 611 | for _, arr := range matches { 612 | name, value := arr[3], arr[1] 613 | if name == "" { 614 | name = "" 615 | } 616 | for pos, pkg := range w.pkgs { 617 | vJ := pkg.(*ast.GenDecl).Specs[0].(*ast.ImportSpec) 618 | if vJ.Path.Value == value && 619 | vJ.Name.String() == name { 620 | w.pkgs = append(w.pkgs[:pos], w.pkgs[pos+1:]...) 621 | w.pkgsNotimport = append(w.pkgsNotimport, pkg) 622 | break 623 | } 624 | } 625 | } 626 | 627 | for _, arr := range regexp.MustCompile(`undefined: (.+)`).FindAllStringSubmatch(err.Error(), -1) { 628 | name := arr[1] 629 | for pos, pkg := range w.pkgsNotimport { 630 | vJ := pkg.(*ast.GenDecl).Specs[0].(*ast.ImportSpec) 631 | if vJ.Name.String() == name || 632 | regexp.MustCompile(`["|/]`+name+`"`).MatchString(vJ.Path.Value) { 633 | w.pkgsNotimport = append(w.pkgsNotimport[:pos], w.pkgsNotimport[pos+1:]...) 634 | w.pkgs = append(w.pkgs, pkg) 635 | break 636 | } 637 | } 638 | } 639 | 640 | err = compile(w) 641 | if err == nil { 642 | goto run 643 | } 644 | 645 | goto restore 646 | 647 | run: 648 | hasOutput, err = run(w) 649 | if err != nil || (!isCodeDefine && hasOutput) { 650 | goto restore 651 | } 652 | return 653 | 654 | restore: 655 | w.pkgs = bkupPkgs 656 | w.pkgsNotimport = bkupPkgsNotimport 657 | w.codes = bkupCodes 658 | w.defs = bkupDefs 659 | w.files = bkupFiles 660 | return 661 | } 662 | 663 | func parseGo4import(w *Workspace, line string) (notComplete bool, err error) { 664 | var tree interface{} 665 | tree, err = parseDeclList(w.files, "gop", line[0:]) 666 | if err != nil { 667 | tree, err = parseStmtList(w.files, "gop", line[0:]) 668 | if err != nil { 669 | if _, ok := err.(scanner.ErrorList); ok { 670 | err = nil 671 | notComplete = true 672 | } 673 | return 674 | } 675 | } 676 | 677 | switch v := tree.(type) { 678 | case []ast.Stmt: 679 | for _, e := range v { 680 | w.codes = append(w.codes, e) 681 | } 682 | case []ast.Decl: 683 | for _, e := range v { 684 | if vI, ok := e.(*ast.GenDecl); ok { 685 | if vI.Tok == token.IMPORT { 686 | for _, spec := range vI.Specs { 687 | name := spec.(*ast.ImportSpec).Name.String() 688 | value := spec.(*ast.ImportSpec).Path.Value 689 | var tree []ast.Decl 690 | if spec.(*ast.ImportSpec).Name == nil { 691 | tree, _ = parseDeclList(w.files, "gop", "import "+value) 692 | } else { 693 | tree, _ = parseDeclList(w.files, "gop", "import "+name+" "+value) 694 | } 695 | w.pkgs = append(w.pkgs, tree[0]) 696 | } 697 | continue 698 | } 699 | } 700 | w.defs = append(w.defs, e) 701 | } 702 | default: 703 | err = errors.New("fatal error: Unknown tree type") 704 | return 705 | } 706 | 707 | return 708 | } 709 | 710 | func dispatch(w *Workspace, line string) (notComplete bool, err error) { 711 | line = strings.TrimSpace(line) 712 | 713 | line = execAlias(w, line) 714 | if line == "" { 715 | return 716 | } 717 | 718 | if execSpecial(w, line) { 719 | return 720 | } 721 | 722 | switch line[0] { 723 | case '?': 724 | fmt.Println("Commands:") 725 | fmt.Println("\t?|help\thelp menu") 726 | fmt.Println("\t-[dpc][#],[#]-[#],...\tpop last/specific (declaration|package|code)") 727 | fmt.Println("\t![!]\tinspect source [with linenum]") 728 | fmt.Println("\ttmpl\twrite tmpl") 730 | fmt.Println("\t[#](...)\tadd def or code") 731 | fmt.Println("\treset\treset") 732 | fmt.Println("\tlist\ttmpl list") 733 | fmt.Println("\targ\tset or get command-line argument") 734 | case '-': 735 | cmdArgs := strings.TrimSpace(line[1:]) 736 | removeByIndex(w, cmdArgs) 737 | case '!': 738 | cmdArgs := strings.TrimSpace(line[1:]) 739 | if cmdArgs == "!" { 740 | fmt.Println(w.source(true, true, true)) 741 | } else { 742 | fmt.Println(w.source(true, false, true)) 743 | } 744 | default: 745 | return parseGo(w, line) 746 | } 747 | 748 | return 749 | } 750 | 751 | func main() { 752 | fmt.Println("Welcome to the Go Partner! [version: 1.7, created by simplejia]") 753 | fmt.Println("Enter '?' for a list of commands.") 754 | 755 | go func() { 756 | signalChan := make(chan os.Signal) 757 | signal.Notify(signalChan, syscall.SIGINT) 758 | for { 759 | <-signalChan 760 | } 761 | }() 762 | 763 | w := &Workspace{ 764 | files: token.NewFileSet(), 765 | } 766 | sourceDefaultDPC(w) 767 | 768 | ifTmplExist, tmplFile := true, "gop.tmpl" 769 | if _, err := os.Stat(filepath.Join(home, tmplFile)); os.IsNotExist(err) { 770 | if _, err := os.Stat(tmplFile); os.IsNotExist(err) { 771 | ifTmplExist = false 772 | } 773 | } 774 | if ifTmplExist { 775 | dispatch(w, "<"+tmplFile) 776 | } 777 | 778 | rl := newContLiner() 779 | defer rl.Close() 780 | 781 | if err := os.MkdirAll(home, 0755); err != nil { 782 | fmt.Println("Mkdir error: ", err) 783 | os.Exit(1) 784 | } 785 | 786 | historyFile := filepath.Join(home, "history") 787 | if f, err := os.Open(historyFile); err != nil { 788 | if !os.IsNotExist(err) { 789 | fmt.Printf("OpenFile %s error: %v\n", historyFile, err) 790 | } 791 | } else { 792 | defer f.Close() 793 | rl.ReadHistory(f) 794 | } 795 | 796 | defer func() { 797 | if f, err := os.Create(historyFile); err != nil { 798 | fmt.Printf("Open %s error: %v\n", historyFile, err) 799 | } else { 800 | rl.WriteHistory(f) 801 | } 802 | }() 803 | 804 | for { 805 | rl.SetWordCompleter(w.completeWord) 806 | 807 | PS1 := "GOP$ " 808 | in, err := rl.Prompt(PS1) 809 | if err != nil { 810 | if err == io.EOF { 811 | break 812 | } else { 813 | fmt.Println("Unexpected error:", err) 814 | continue 815 | } 816 | } 817 | 818 | if in == "" { 819 | continue 820 | } 821 | 822 | rl.Reindent() 823 | 824 | notComplete, err := dispatch(w, in) 825 | if err != nil { 826 | fmt.Println("Error:", err) 827 | } else if notComplete { 828 | continue 829 | } 830 | 831 | rl.Accepted() 832 | } 833 | } 834 | --------------------------------------------------------------------------------