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  }