github.com/yunabe/lgo@v0.0.0-20190709125917-42c42d410fdf/cmd/lgo-internal/liner/liner.go (about) 1 package liner 2 3 import ( 4 "go/ast" 5 "go/scanner" 6 "go/token" 7 "io" 8 "strings" 9 10 "github.com/peterh/liner" 11 "github.com/yunabe/lgo/parser" 12 ) 13 14 func parseLesserGoString(src string) (f *parser.LGOBlock, err error) { 15 return parser.ParseLesserGoFile(token.NewFileSet(), "", src, 0) 16 } 17 18 func isUnexpectedEOF(errs scanner.ErrorList, lines []string) bool { 19 for _, err := range errs { 20 if err.Msg == "raw string literal not terminated" || err.Msg == "comment not terminated" { 21 return true 22 } 23 if strings.Contains(err.Msg, "expected ") && err.Pos.Line == len(lines) && 24 err.Pos.Column == len(lines[len(lines)-1])+1 { 25 return true 26 } 27 } 28 return false 29 } 30 31 func nextIndent(src string) int { 32 sc := &scanner.Scanner{} 33 fs := token.NewFileSet() 34 var msgs []string 35 sc.Init(fs.AddFile("", -1, len(src)), []byte(src), func(_ token.Position, msg string) { 36 msgs = append(msgs, msg) 37 }, 0) 38 var indent int 39 for { 40 _, tok, _ := sc.Scan() 41 if tok == token.EOF { 42 break 43 } 44 if tok == token.LBRACE { 45 indent++ 46 } else if indent > 0 && tok == token.RBRACE { 47 indent-- 48 } 49 } 50 return indent 51 } 52 53 func dropEmptyLine(lines []string) []string { 54 r := make([]string, 0, len(lines)) 55 for _, line := range lines { 56 if len(line) > 0 { 57 r = append(r, line) 58 } 59 } 60 return r 61 } 62 63 func hasTypeOrMethodDecl(b *parser.LGOBlock) bool { 64 for _, s := range b.Stmts { 65 decl, _ := s.(*ast.DeclStmt) 66 if decl == nil { 67 continue 68 } 69 if f, _ := decl.Decl.(*ast.FuncDecl); f != nil && f.Recv != nil { 70 // methods 71 return true 72 } 73 if gen, _ := decl.Decl.(*ast.GenDecl); gen != nil && gen.Tok == token.TYPE { 74 // type decl 75 return true 76 } 77 } 78 return false 79 } 80 81 // continueForMethods returns true if lines have type or method declaration and the last line is not empty. 82 func continueForMethods(b *parser.LGOBlock, lines []string) (bool, int) { 83 if len(lines) == 0 || !hasTypeOrMethodDecl(b) { 84 // Note: len(lines) check exists just for safety. 85 return false, 0 86 } 87 last := lines[len(lines)-1] 88 if strings.TrimSpace(last) == "" { 89 return false, 0 90 } 91 return true, 0 92 } 93 94 func continueLine(lines []string) (bool, int) { 95 dropped := dropEmptyLine(lines) 96 src := strings.Join(dropped, "\n") 97 b, err := parseLesserGoString(src) 98 if err == nil { 99 return continueForMethods(b, lines) 100 } 101 if errs, ok := err.(scanner.ErrorList); !ok || !isUnexpectedEOF(errs, dropped) { 102 return continueForMethods(b, lines) 103 } 104 return true, nextIndent(src) 105 } 106 107 func ContinueLineString(s string) (bool, int) { 108 return continueLine(strings.Split(s, "\n")) 109 } 110 111 type Liner struct { 112 liner *liner.State 113 // lines keeps the intermediate input to use it from complete 114 lines []string 115 } 116 117 func NewLiner() *Liner { 118 return &Liner{ 119 liner: liner.NewLiner(), 120 } 121 } 122 123 func (l *Liner) Next() (string, error) { 124 l.lines = nil 125 var indent int 126 for { 127 var prompt string 128 if l.lines == nil { 129 prompt = ">>> " 130 } else { 131 prompt = "... " 132 } 133 if indent > 0 { 134 prompt += strings.Repeat(" ", int(indent)) 135 } 136 // line does not include \n. 137 line, err := l.liner.Prompt(prompt) 138 if err == io.EOF { 139 // Ctrl-D 140 if l.lines == nil { 141 return "", io.EOF 142 } 143 return "", nil 144 } 145 l.lines = append(l.lines, line) 146 var cont bool 147 cont, indent = continueLine(l.lines) 148 if !cont { 149 content := strings.Join(l.lines, "\n") 150 if len(content) > 0 { 151 l.liner.AppendHistory(content) 152 } 153 return content, nil 154 } 155 } 156 } 157 158 // SetCompleter sets the completion function that Liner will call to fetch completion candidates when the user presses tab. 159 func (l *Liner) SetCompleter(f func(lines []string) []string) { 160 l.liner.SetCompleter(func(line string) []string { 161 return f(append(l.lines, line)) 162 }) 163 }