--- /dev/null
+// Copyright 2013 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 main
+
+// This file defines utilities for working with file positions.
+
+import (
+ "fmt"
+ "go/build"
+ "go/parser"
+ "go/token"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "golang.org/x/tools/go/ast/astutil"
+ "golang.org/x/tools/go/buildutil"
+)
+
+// parseOctothorpDecimal returns the numeric value if s matches "#%d",
+// otherwise -1.
+func parseOctothorpDecimal(s string) int {
+ if s != "" && s[0] == '#' {
+ if s, err := strconv.ParseInt(s[1:], 10, 32); err == nil {
+ return int(s)
+ }
+ }
+ return -1
+}
+
+// parsePos parses a string of the form "file:pos" or
+// file:start,end" where pos, start, end match #%d and represent byte
+// offsets, and returns its components.
+//
+// (Numbers without a '#' prefix are reserved for future use,
+// e.g. to indicate line/column positions.)
+//
+func parsePos(pos string) (filename string, startOffset, endOffset int, err error) {
+ if pos == "" {
+ err = fmt.Errorf("no source position specified")
+ return
+ }
+
+ colon := strings.LastIndex(pos, ":")
+ if colon < 0 {
+ err = fmt.Errorf("bad position syntax %q", pos)
+ return
+ }
+ filename, offset := pos[:colon], pos[colon+1:]
+ startOffset = -1
+ endOffset = -1
+ if comma := strings.Index(offset, ","); comma < 0 {
+ // e.g. "foo.go:#123"
+ startOffset = parseOctothorpDecimal(offset)
+ endOffset = startOffset
+ } else {
+ // e.g. "foo.go:#123,#456"
+ startOffset = parseOctothorpDecimal(offset[:comma])
+ endOffset = parseOctothorpDecimal(offset[comma+1:])
+ }
+ if startOffset < 0 || endOffset < 0 {
+ err = fmt.Errorf("invalid offset %q in query position", offset)
+ return
+ }
+ return
+}
+
+// fileOffsetToPos translates the specified file-relative byte offsets
+// into token.Pos form. It returns an error if the file was not found
+// or the offsets were out of bounds.
+//
+func fileOffsetToPos(file *token.File, startOffset, endOffset int) (start, end token.Pos, err error) {
+ // Range check [start..end], inclusive of both end-points.
+
+ if 0 <= startOffset && startOffset <= file.Size() {
+ start = file.Pos(int(startOffset))
+ } else {
+ err = fmt.Errorf("start position is beyond end of file")
+ return
+ }
+
+ if 0 <= endOffset && endOffset <= file.Size() {
+ end = file.Pos(int(endOffset))
+ } else {
+ err = fmt.Errorf("end position is beyond end of file")
+ return
+ }
+
+ return
+}
+
+// sameFile returns true if x and y have the same basename and denote
+// the same file.
+//
+func sameFile(x, y string) bool {
+ if filepath.Base(x) == filepath.Base(y) { // (optimisation)
+ if xi, err := os.Stat(x); err == nil {
+ if yi, err := os.Stat(y); err == nil {
+ return os.SameFile(xi, yi)
+ }
+ }
+ }
+ return false
+}
+
+// fastQueryPos parses the position string and returns a queryPos.
+// It parses only a single file and does not run the type checker.
+func fastQueryPos(ctxt *build.Context, pos string) (*queryPos, error) {
+ filename, startOffset, endOffset, err := parsePos(pos)
+ if err != nil {
+ return nil, err
+ }
+
+ // Parse the file, opening it the file via the build.Context
+ // so that we observe the effects of the -modified flag.
+ fset := token.NewFileSet()
+ cwd, _ := os.Getwd()
+ f, err := buildutil.ParseFile(fset, ctxt, nil, cwd, filename, parser.Mode(0))
+ // ParseFile usually returns a partial file along with an error.
+ // Only fail if there is no file.
+ if f == nil {
+ return nil, err
+ }
+ if !f.Pos().IsValid() {
+ return nil, fmt.Errorf("%s is not a Go source file", filename)
+ }
+
+ start, end, err := fileOffsetToPos(fset.File(f.Pos()), startOffset, endOffset)
+ if err != nil {
+ return nil, err
+ }
+
+ path, exact := astutil.PathEnclosingInterval(f, start, end)
+ if path == nil {
+ return nil, fmt.Errorf("no syntax here")
+ }
+
+ return &queryPos{fset, start, end, path, exact, nil}, nil
+}