Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / mod@v0.3.0 / modfile / read.go
diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/mod@v0.3.0/modfile/read.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/mod@v0.3.0/modfile/read.go
new file mode 100644 (file)
index 0000000..c1f2008
--- /dev/null
@@ -0,0 +1,948 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package modfile
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "os"
+       "strconv"
+       "strings"
+       "unicode"
+       "unicode/utf8"
+)
+
+// A Position describes an arbitrary source position in a file, including the
+// file, line, column, and byte offset.
+type Position struct {
+       Line     int // line in input (starting at 1)
+       LineRune int // rune in line (starting at 1)
+       Byte     int // byte in input (starting at 0)
+}
+
+// add returns the position at the end of s, assuming it starts at p.
+func (p Position) add(s string) Position {
+       p.Byte += len(s)
+       if n := strings.Count(s, "\n"); n > 0 {
+               p.Line += n
+               s = s[strings.LastIndex(s, "\n")+1:]
+               p.LineRune = 1
+       }
+       p.LineRune += utf8.RuneCountInString(s)
+       return p
+}
+
+// An Expr represents an input element.
+type Expr interface {
+       // Span returns the start and end position of the expression,
+       // excluding leading or trailing comments.
+       Span() (start, end Position)
+
+       // Comment returns the comments attached to the expression.
+       // This method would normally be named 'Comments' but that
+       // would interfere with embedding a type of the same name.
+       Comment() *Comments
+}
+
+// A Comment represents a single // comment.
+type Comment struct {
+       Start  Position
+       Token  string // without trailing newline
+       Suffix bool   // an end of line (not whole line) comment
+}
+
+// Comments collects the comments associated with an expression.
+type Comments struct {
+       Before []Comment // whole-line comments before this expression
+       Suffix []Comment // end-of-line comments after this expression
+
+       // For top-level expressions only, After lists whole-line
+       // comments following the expression.
+       After []Comment
+}
+
+// Comment returns the receiver. This isn't useful by itself, but
+// a Comments struct is embedded into all the expression
+// implementation types, and this gives each of those a Comment
+// method to satisfy the Expr interface.
+func (c *Comments) Comment() *Comments {
+       return c
+}
+
+// A FileSyntax represents an entire go.mod file.
+type FileSyntax struct {
+       Name string // file path
+       Comments
+       Stmt []Expr
+}
+
+func (x *FileSyntax) Span() (start, end Position) {
+       if len(x.Stmt) == 0 {
+               return
+       }
+       start, _ = x.Stmt[0].Span()
+       _, end = x.Stmt[len(x.Stmt)-1].Span()
+       return start, end
+}
+
+// addLine adds a line containing the given tokens to the file.
+//
+// If the first token of the hint matches the first token of the
+// line, the new line is added at the end of the block containing hint,
+// extracting hint into a new block if it is not yet in one.
+//
+// If the hint is non-nil buts its first token does not match,
+// the new line is added after the block containing hint
+// (or hint itself, if not in a block).
+//
+// If no hint is provided, addLine appends the line to the end of
+// the last block with a matching first token,
+// or to the end of the file if no such block exists.
+func (x *FileSyntax) addLine(hint Expr, tokens ...string) *Line {
+       if hint == nil {
+               // If no hint given, add to the last statement of the given type.
+       Loop:
+               for i := len(x.Stmt) - 1; i >= 0; i-- {
+                       stmt := x.Stmt[i]
+                       switch stmt := stmt.(type) {
+                       case *Line:
+                               if stmt.Token != nil && stmt.Token[0] == tokens[0] {
+                                       hint = stmt
+                                       break Loop
+                               }
+                       case *LineBlock:
+                               if stmt.Token[0] == tokens[0] {
+                                       hint = stmt
+                                       break Loop
+                               }
+                       }
+               }
+       }
+
+       newLineAfter := func(i int) *Line {
+               new := &Line{Token: tokens}
+               if i == len(x.Stmt) {
+                       x.Stmt = append(x.Stmt, new)
+               } else {
+                       x.Stmt = append(x.Stmt, nil)
+                       copy(x.Stmt[i+2:], x.Stmt[i+1:])
+                       x.Stmt[i+1] = new
+               }
+               return new
+       }
+
+       if hint != nil {
+               for i, stmt := range x.Stmt {
+                       switch stmt := stmt.(type) {
+                       case *Line:
+                               if stmt == hint {
+                                       if stmt.Token == nil || stmt.Token[0] != tokens[0] {
+                                               return newLineAfter(i)
+                                       }
+
+                                       // Convert line to line block.
+                                       stmt.InBlock = true
+                                       block := &LineBlock{Token: stmt.Token[:1], Line: []*Line{stmt}}
+                                       stmt.Token = stmt.Token[1:]
+                                       x.Stmt[i] = block
+                                       new := &Line{Token: tokens[1:], InBlock: true}
+                                       block.Line = append(block.Line, new)
+                                       return new
+                               }
+
+                       case *LineBlock:
+                               if stmt == hint {
+                                       if stmt.Token[0] != tokens[0] {
+                                               return newLineAfter(i)
+                                       }
+
+                                       new := &Line{Token: tokens[1:], InBlock: true}
+                                       stmt.Line = append(stmt.Line, new)
+                                       return new
+                               }
+
+                               for j, line := range stmt.Line {
+                                       if line == hint {
+                                               if stmt.Token[0] != tokens[0] {
+                                                       return newLineAfter(i)
+                                               }
+
+                                               // Add new line after hint within the block.
+                                               stmt.Line = append(stmt.Line, nil)
+                                               copy(stmt.Line[j+2:], stmt.Line[j+1:])
+                                               new := &Line{Token: tokens[1:], InBlock: true}
+                                               stmt.Line[j+1] = new
+                                               return new
+                                       }
+                               }
+                       }
+               }
+       }
+
+       new := &Line{Token: tokens}
+       x.Stmt = append(x.Stmt, new)
+       return new
+}
+
+func (x *FileSyntax) updateLine(line *Line, tokens ...string) {
+       if line.InBlock {
+               tokens = tokens[1:]
+       }
+       line.Token = tokens
+}
+
+func (x *FileSyntax) removeLine(line *Line) {
+       line.Token = nil
+}
+
+// Cleanup cleans up the file syntax x after any edit operations.
+// To avoid quadratic behavior, removeLine marks the line as dead
+// by setting line.Token = nil but does not remove it from the slice
+// in which it appears. After edits have all been indicated,
+// calling Cleanup cleans out the dead lines.
+func (x *FileSyntax) Cleanup() {
+       w := 0
+       for _, stmt := range x.Stmt {
+               switch stmt := stmt.(type) {
+               case *Line:
+                       if stmt.Token == nil {
+                               continue
+                       }
+               case *LineBlock:
+                       ww := 0
+                       for _, line := range stmt.Line {
+                               if line.Token != nil {
+                                       stmt.Line[ww] = line
+                                       ww++
+                               }
+                       }
+                       if ww == 0 {
+                               continue
+                       }
+                       if ww == 1 {
+                               // Collapse block into single line.
+                               line := &Line{
+                                       Comments: Comments{
+                                               Before: commentsAdd(stmt.Before, stmt.Line[0].Before),
+                                               Suffix: commentsAdd(stmt.Line[0].Suffix, stmt.Suffix),
+                                               After:  commentsAdd(stmt.Line[0].After, stmt.After),
+                                       },
+                                       Token: stringsAdd(stmt.Token, stmt.Line[0].Token),
+                               }
+                               x.Stmt[w] = line
+                               w++
+                               continue
+                       }
+                       stmt.Line = stmt.Line[:ww]
+               }
+               x.Stmt[w] = stmt
+               w++
+       }
+       x.Stmt = x.Stmt[:w]
+}
+
+func commentsAdd(x, y []Comment) []Comment {
+       return append(x[:len(x):len(x)], y...)
+}
+
+func stringsAdd(x, y []string) []string {
+       return append(x[:len(x):len(x)], y...)
+}
+
+// A CommentBlock represents a top-level block of comments separate
+// from any rule.
+type CommentBlock struct {
+       Comments
+       Start Position
+}
+
+func (x *CommentBlock) Span() (start, end Position) {
+       return x.Start, x.Start
+}
+
+// A Line is a single line of tokens.
+type Line struct {
+       Comments
+       Start   Position
+       Token   []string
+       InBlock bool
+       End     Position
+}
+
+func (x *Line) Span() (start, end Position) {
+       return x.Start, x.End
+}
+
+// A LineBlock is a factored block of lines, like
+//
+//     require (
+//             "x"
+//             "y"
+//     )
+//
+type LineBlock struct {
+       Comments
+       Start  Position
+       LParen LParen
+       Token  []string
+       Line   []*Line
+       RParen RParen
+}
+
+func (x *LineBlock) Span() (start, end Position) {
+       return x.Start, x.RParen.Pos.add(")")
+}
+
+// An LParen represents the beginning of a parenthesized line block.
+// It is a place to store suffix comments.
+type LParen struct {
+       Comments
+       Pos Position
+}
+
+func (x *LParen) Span() (start, end Position) {
+       return x.Pos, x.Pos.add(")")
+}
+
+// An RParen represents the end of a parenthesized line block.
+// It is a place to store whole-line (before) comments.
+type RParen struct {
+       Comments
+       Pos Position
+}
+
+func (x *RParen) Span() (start, end Position) {
+       return x.Pos, x.Pos.add(")")
+}
+
+// An input represents a single input file being parsed.
+type input struct {
+       // Lexing state.
+       filename   string    // name of input file, for errors
+       complete   []byte    // entire input
+       remaining  []byte    // remaining input
+       tokenStart []byte    // token being scanned to end of input
+       token      token     // next token to be returned by lex, peek
+       pos        Position  // current input position
+       comments   []Comment // accumulated comments
+
+       // Parser state.
+       file        *FileSyntax // returned top-level syntax tree
+       parseErrors ErrorList   // errors encountered during parsing
+
+       // Comment assignment state.
+       pre  []Expr // all expressions, in preorder traversal
+       post []Expr // all expressions, in postorder traversal
+}
+
+func newInput(filename string, data []byte) *input {
+       return &input{
+               filename:  filename,
+               complete:  data,
+               remaining: data,
+               pos:       Position{Line: 1, LineRune: 1, Byte: 0},
+       }
+}
+
+// parse parses the input file.
+func parse(file string, data []byte) (f *FileSyntax, err error) {
+       // The parser panics for both routine errors like syntax errors
+       // and for programmer bugs like array index errors.
+       // Turn both into error returns. Catching bug panics is
+       // especially important when processing many files.
+       in := newInput(file, data)
+       defer func() {
+               if e := recover(); e != nil && e != &in.parseErrors {
+                       in.parseErrors = append(in.parseErrors, Error{
+                               Filename: in.filename,
+                               Pos:      in.pos,
+                               Err:      fmt.Errorf("internal error: %v", e),
+                       })
+               }
+               if err == nil && len(in.parseErrors) > 0 {
+                       err = in.parseErrors
+               }
+       }()
+
+       // Prime the lexer by reading in the first token. It will be available
+       // in the next peek() or lex() call.
+       in.readToken()
+
+       // Invoke the parser.
+       in.parseFile()
+       if len(in.parseErrors) > 0 {
+               return nil, in.parseErrors
+       }
+       in.file.Name = in.filename
+
+       // Assign comments to nearby syntax.
+       in.assignComments()
+
+       return in.file, nil
+}
+
+// Error is called to report an error.
+// Error does not return: it panics.
+func (in *input) Error(s string) {
+       in.parseErrors = append(in.parseErrors, Error{
+               Filename: in.filename,
+               Pos:      in.pos,
+               Err:      errors.New(s),
+       })
+       panic(&in.parseErrors)
+}
+
+// eof reports whether the input has reached end of file.
+func (in *input) eof() bool {
+       return len(in.remaining) == 0
+}
+
+// peekRune returns the next rune in the input without consuming it.
+func (in *input) peekRune() int {
+       if len(in.remaining) == 0 {
+               return 0
+       }
+       r, _ := utf8.DecodeRune(in.remaining)
+       return int(r)
+}
+
+// peekPrefix reports whether the remaining input begins with the given prefix.
+func (in *input) peekPrefix(prefix string) bool {
+       // This is like bytes.HasPrefix(in.remaining, []byte(prefix))
+       // but without the allocation of the []byte copy of prefix.
+       for i := 0; i < len(prefix); i++ {
+               if i >= len(in.remaining) || in.remaining[i] != prefix[i] {
+                       return false
+               }
+       }
+       return true
+}
+
+// readRune consumes and returns the next rune in the input.
+func (in *input) readRune() int {
+       if len(in.remaining) == 0 {
+               in.Error("internal lexer error: readRune at EOF")
+       }
+       r, size := utf8.DecodeRune(in.remaining)
+       in.remaining = in.remaining[size:]
+       if r == '\n' {
+               in.pos.Line++
+               in.pos.LineRune = 1
+       } else {
+               in.pos.LineRune++
+       }
+       in.pos.Byte += size
+       return int(r)
+}
+
+type token struct {
+       kind   tokenKind
+       pos    Position
+       endPos Position
+       text   string
+}
+
+type tokenKind int
+
+const (
+       _EOF tokenKind = -(iota + 1)
+       _EOLCOMMENT
+       _IDENT
+       _STRING
+       _COMMENT
+
+       // newlines and punctuation tokens are allowed as ASCII codes.
+)
+
+func (k tokenKind) isComment() bool {
+       return k == _COMMENT || k == _EOLCOMMENT
+}
+
+// isEOL returns whether a token terminates a line.
+func (k tokenKind) isEOL() bool {
+       return k == _EOF || k == _EOLCOMMENT || k == '\n'
+}
+
+// startToken marks the beginning of the next input token.
+// It must be followed by a call to endToken, once the token's text has
+// been consumed using readRune.
+func (in *input) startToken() {
+       in.tokenStart = in.remaining
+       in.token.text = ""
+       in.token.pos = in.pos
+}
+
+// endToken marks the end of an input token.
+// It records the actual token string in tok.text.
+func (in *input) endToken(kind tokenKind) {
+       in.token.kind = kind
+       text := string(in.tokenStart[:len(in.tokenStart)-len(in.remaining)])
+       in.token.text = text
+       in.token.endPos = in.pos
+}
+
+// peek returns the kind of the the next token returned by lex.
+func (in *input) peek() tokenKind {
+       return in.token.kind
+}
+
+// lex is called from the parser to obtain the next input token.
+func (in *input) lex() token {
+       tok := in.token
+       in.readToken()
+       return tok
+}
+
+// readToken lexes the next token from the text and stores it in in.token.
+func (in *input) readToken() {
+       // Skip past spaces, stopping at non-space or EOF.
+       for !in.eof() {
+               c := in.peekRune()
+               if c == ' ' || c == '\t' || c == '\r' {
+                       in.readRune()
+                       continue
+               }
+
+               // Comment runs to end of line.
+               if in.peekPrefix("//") {
+                       in.startToken()
+
+                       // Is this comment the only thing on its line?
+                       // Find the last \n before this // and see if it's all
+                       // spaces from there to here.
+                       i := bytes.LastIndex(in.complete[:in.pos.Byte], []byte("\n"))
+                       suffix := len(bytes.TrimSpace(in.complete[i+1:in.pos.Byte])) > 0
+                       in.readRune()
+                       in.readRune()
+
+                       // Consume comment.
+                       for len(in.remaining) > 0 && in.readRune() != '\n' {
+                       }
+
+                       // If we are at top level (not in a statement), hand the comment to
+                       // the parser as a _COMMENT token. The grammar is written
+                       // to handle top-level comments itself.
+                       if !suffix {
+                               in.endToken(_COMMENT)
+                               return
+                       }
+
+                       // Otherwise, save comment for later attachment to syntax tree.
+                       in.endToken(_EOLCOMMENT)
+                       in.comments = append(in.comments, Comment{in.token.pos, in.token.text, suffix})
+                       return
+               }
+
+               if in.peekPrefix("/*") {
+                       in.Error("mod files must use // comments (not /* */ comments)")
+               }
+
+               // Found non-space non-comment.
+               break
+       }
+
+       // Found the beginning of the next token.
+       in.startToken()
+
+       // End of file.
+       if in.eof() {
+               in.endToken(_EOF)
+               return
+       }
+
+       // Punctuation tokens.
+       switch c := in.peekRune(); c {
+       case '\n', '(', ')', '[', ']', '{', '}', ',':
+               in.readRune()
+               in.endToken(tokenKind(c))
+               return
+
+       case '"', '`': // quoted string
+               quote := c
+               in.readRune()
+               for {
+                       if in.eof() {
+                               in.pos = in.token.pos
+                               in.Error("unexpected EOF in string")
+                       }
+                       if in.peekRune() == '\n' {
+                               in.Error("unexpected newline in string")
+                       }
+                       c := in.readRune()
+                       if c == quote {
+                               break
+                       }
+                       if c == '\\' && quote != '`' {
+                               if in.eof() {
+                                       in.pos = in.token.pos
+                                       in.Error("unexpected EOF in string")
+                               }
+                               in.readRune()
+                       }
+               }
+               in.endToken(_STRING)
+               return
+       }
+
+       // Checked all punctuation. Must be identifier token.
+       if c := in.peekRune(); !isIdent(c) {
+               in.Error(fmt.Sprintf("unexpected input character %#q", c))
+       }
+
+       // Scan over identifier.
+       for isIdent(in.peekRune()) {
+               if in.peekPrefix("//") {
+                       break
+               }
+               if in.peekPrefix("/*") {
+                       in.Error("mod files must use // comments (not /* */ comments)")
+               }
+               in.readRune()
+       }
+       in.endToken(_IDENT)
+}
+
+// isIdent reports whether c is an identifier rune.
+// We treat most printable runes as identifier runes, except for a handful of
+// ASCII punctuation characters.
+func isIdent(c int) bool {
+       switch r := rune(c); r {
+       case ' ', '(', ')', '[', ']', '{', '}', ',':
+               return false
+       default:
+               return !unicode.IsSpace(r) && unicode.IsPrint(r)
+       }
+}
+
+// Comment assignment.
+// We build two lists of all subexpressions, preorder and postorder.
+// The preorder list is ordered by start location, with outer expressions first.
+// The postorder list is ordered by end location, with outer expressions last.
+// We use the preorder list to assign each whole-line comment to the syntax
+// immediately following it, and we use the postorder list to assign each
+// end-of-line comment to the syntax immediately preceding it.
+
+// order walks the expression adding it and its subexpressions to the
+// preorder and postorder lists.
+func (in *input) order(x Expr) {
+       if x != nil {
+               in.pre = append(in.pre, x)
+       }
+       switch x := x.(type) {
+       default:
+               panic(fmt.Errorf("order: unexpected type %T", x))
+       case nil:
+               // nothing
+       case *LParen, *RParen:
+               // nothing
+       case *CommentBlock:
+               // nothing
+       case *Line:
+               // nothing
+       case *FileSyntax:
+               for _, stmt := range x.Stmt {
+                       in.order(stmt)
+               }
+       case *LineBlock:
+               in.order(&x.LParen)
+               for _, l := range x.Line {
+                       in.order(l)
+               }
+               in.order(&x.RParen)
+       }
+       if x != nil {
+               in.post = append(in.post, x)
+       }
+}
+
+// assignComments attaches comments to nearby syntax.
+func (in *input) assignComments() {
+       const debug = false
+
+       // Generate preorder and postorder lists.
+       in.order(in.file)
+
+       // Split into whole-line comments and suffix comments.
+       var line, suffix []Comment
+       for _, com := range in.comments {
+               if com.Suffix {
+                       suffix = append(suffix, com)
+               } else {
+                       line = append(line, com)
+               }
+       }
+
+       if debug {
+               for _, c := range line {
+                       fmt.Fprintf(os.Stderr, "LINE %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte)
+               }
+       }
+
+       // Assign line comments to syntax immediately following.
+       for _, x := range in.pre {
+               start, _ := x.Span()
+               if debug {
+                       fmt.Fprintf(os.Stderr, "pre %T :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte)
+               }
+               xcom := x.Comment()
+               for len(line) > 0 && start.Byte >= line[0].Start.Byte {
+                       if debug {
+                               fmt.Fprintf(os.Stderr, "ASSIGN LINE %q #%d\n", line[0].Token, line[0].Start.Byte)
+                       }
+                       xcom.Before = append(xcom.Before, line[0])
+                       line = line[1:]
+               }
+       }
+
+       // Remaining line comments go at end of file.
+       in.file.After = append(in.file.After, line...)
+
+       if debug {
+               for _, c := range suffix {
+                       fmt.Fprintf(os.Stderr, "SUFFIX %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte)
+               }
+       }
+
+       // Assign suffix comments to syntax immediately before.
+       for i := len(in.post) - 1; i >= 0; i-- {
+               x := in.post[i]
+
+               start, end := x.Span()
+               if debug {
+                       fmt.Fprintf(os.Stderr, "post %T :%d:%d #%d :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte, end.Line, end.LineRune, end.Byte)
+               }
+
+               // Do not assign suffix comments to end of line block or whole file.
+               // Instead assign them to the last element inside.
+               switch x.(type) {
+               case *FileSyntax:
+                       continue
+               }
+
+               // Do not assign suffix comments to something that starts
+               // on an earlier line, so that in
+               //
+               //      x ( y
+               //              z ) // comment
+               //
+               // we assign the comment to z and not to x ( ... ).
+               if start.Line != end.Line {
+                       continue
+               }
+               xcom := x.Comment()
+               for len(suffix) > 0 && end.Byte <= suffix[len(suffix)-1].Start.Byte {
+                       if debug {
+                               fmt.Fprintf(os.Stderr, "ASSIGN SUFFIX %q #%d\n", suffix[len(suffix)-1].Token, suffix[len(suffix)-1].Start.Byte)
+                       }
+                       xcom.Suffix = append(xcom.Suffix, suffix[len(suffix)-1])
+                       suffix = suffix[:len(suffix)-1]
+               }
+       }
+
+       // We assigned suffix comments in reverse.
+       // If multiple suffix comments were appended to the same
+       // expression node, they are now in reverse. Fix that.
+       for _, x := range in.post {
+               reverseComments(x.Comment().Suffix)
+       }
+
+       // Remaining suffix comments go at beginning of file.
+       in.file.Before = append(in.file.Before, suffix...)
+}
+
+// reverseComments reverses the []Comment list.
+func reverseComments(list []Comment) {
+       for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
+               list[i], list[j] = list[j], list[i]
+       }
+}
+
+func (in *input) parseFile() {
+       in.file = new(FileSyntax)
+       var cb *CommentBlock
+       for {
+               switch in.peek() {
+               case '\n':
+                       in.lex()
+                       if cb != nil {
+                               in.file.Stmt = append(in.file.Stmt, cb)
+                               cb = nil
+                       }
+               case _COMMENT:
+                       tok := in.lex()
+                       if cb == nil {
+                               cb = &CommentBlock{Start: tok.pos}
+                       }
+                       com := cb.Comment()
+                       com.Before = append(com.Before, Comment{Start: tok.pos, Token: tok.text})
+               case _EOF:
+                       if cb != nil {
+                               in.file.Stmt = append(in.file.Stmt, cb)
+                       }
+                       return
+               default:
+                       in.parseStmt()
+                       if cb != nil {
+                               in.file.Stmt[len(in.file.Stmt)-1].Comment().Before = cb.Before
+                               cb = nil
+                       }
+               }
+       }
+}
+
+func (in *input) parseStmt() {
+       tok := in.lex()
+       start := tok.pos
+       end := tok.endPos
+       tokens := []string{tok.text}
+       for {
+               tok := in.lex()
+               switch {
+               case tok.kind.isEOL():
+                       in.file.Stmt = append(in.file.Stmt, &Line{
+                               Start: start,
+                               Token: tokens,
+                               End:   end,
+                       })
+                       return
+
+               case tok.kind == '(':
+                       if next := in.peek(); next.isEOL() {
+                               // Start of block: no more tokens on this line.
+                               in.file.Stmt = append(in.file.Stmt, in.parseLineBlock(start, tokens, tok))
+                               return
+                       } else if next == ')' {
+                               rparen := in.lex()
+                               if in.peek().isEOL() {
+                                       // Empty block.
+                                       in.lex()
+                                       in.file.Stmt = append(in.file.Stmt, &LineBlock{
+                                               Start:  start,
+                                               Token:  tokens,
+                                               LParen: LParen{Pos: tok.pos},
+                                               RParen: RParen{Pos: rparen.pos},
+                                       })
+                                       return
+                               }
+                               // '( )' in the middle of the line, not a block.
+                               tokens = append(tokens, tok.text, rparen.text)
+                       } else {
+                               // '(' in the middle of the line, not a block.
+                               tokens = append(tokens, tok.text)
+                       }
+
+               default:
+                       tokens = append(tokens, tok.text)
+                       end = tok.endPos
+               }
+       }
+}
+
+func (in *input) parseLineBlock(start Position, token []string, lparen token) *LineBlock {
+       x := &LineBlock{
+               Start:  start,
+               Token:  token,
+               LParen: LParen{Pos: lparen.pos},
+       }
+       var comments []Comment
+       for {
+               switch in.peek() {
+               case _EOLCOMMENT:
+                       // Suffix comment, will be attached later by assignComments.
+                       in.lex()
+               case '\n':
+                       // Blank line. Add an empty comment to preserve it.
+                       in.lex()
+                       if len(comments) == 0 && len(x.Line) > 0 || len(comments) > 0 && comments[len(comments)-1].Token != "" {
+                               comments = append(comments, Comment{})
+                       }
+               case _COMMENT:
+                       tok := in.lex()
+                       comments = append(comments, Comment{Start: tok.pos, Token: tok.text})
+               case _EOF:
+                       in.Error(fmt.Sprintf("syntax error (unterminated block started at %s:%d:%d)", in.filename, x.Start.Line, x.Start.LineRune))
+               case ')':
+                       rparen := in.lex()
+                       x.RParen.Before = comments
+                       x.RParen.Pos = rparen.pos
+                       if !in.peek().isEOL() {
+                               in.Error("syntax error (expected newline after closing paren)")
+                       }
+                       in.lex()
+                       return x
+               default:
+                       l := in.parseLine()
+                       x.Line = append(x.Line, l)
+                       l.Comment().Before = comments
+                       comments = nil
+               }
+       }
+}
+
+func (in *input) parseLine() *Line {
+       tok := in.lex()
+       if tok.kind.isEOL() {
+               in.Error("internal parse error: parseLine at end of line")
+       }
+       start := tok.pos
+       end := tok.endPos
+       tokens := []string{tok.text}
+       for {
+               tok := in.lex()
+               if tok.kind.isEOL() {
+                       return &Line{
+                               Start:   start,
+                               Token:   tokens,
+                               End:     end,
+                               InBlock: true,
+                       }
+               }
+               tokens = append(tokens, tok.text)
+               end = tok.endPos
+       }
+}
+
+var (
+       slashSlash = []byte("//")
+       moduleStr  = []byte("module")
+)
+
+// ModulePath returns the module path from the gomod file text.
+// If it cannot find a module path, it returns an empty string.
+// It is tolerant of unrelated problems in the go.mod file.
+func ModulePath(mod []byte) string {
+       for len(mod) > 0 {
+               line := mod
+               mod = nil
+               if i := bytes.IndexByte(line, '\n'); i >= 0 {
+                       line, mod = line[:i], line[i+1:]
+               }
+               if i := bytes.Index(line, slashSlash); i >= 0 {
+                       line = line[:i]
+               }
+               line = bytes.TrimSpace(line)
+               if !bytes.HasPrefix(line, moduleStr) {
+                       continue
+               }
+               line = line[len(moduleStr):]
+               n := len(line)
+               line = bytes.TrimSpace(line)
+               if len(line) == n || len(line) == 0 {
+                       continue
+               }
+
+               if line[0] == '"' || line[0] == '`' {
+                       p, err := strconv.Unquote(string(line))
+                       if err != nil {
+                               return "" // malformed quoted string or multiline module path
+                       }
+                       return p
+               }
+
+               return string(line)
+       }
+       return "" // missing module path
+}