package token12import (3"fmt"4"sort"5"strconv"6)78// NoPos is the zero value for Pos. It has no file or line information9// associated with it, and NoPos.Valid is false.10var NoPos = Pos{}1112// Pos is a compact representation of a position within a file. It can be13// converted into a Position for a more convenient, but larger, representation.14type Pos struct {15file *File16off int17}1819// String returns the string form of the Pos (the offset).20func (p Pos) String() string { return strconv.Itoa(p.off) }2122// File returns the file used by the Pos. This will be nil for invalid23// positions.24func (p Pos) File() *File { return p.file }2526// Position converts the Pos into a Position.27func (p Pos) Position() Position { return p.file.PositionFor(p) }2829// Add creates a new Pos relative to p.30func (p Pos) Add(n int) Pos {31return Pos{32file: p.file,33off: p.off + n,34}35}3637// Offset returns the byte offset associated with Pos.38func (p Pos) Offset() int { return p.off }3940// Valid reports whether the Pos is valid.41func (p Pos) Valid() bool { return p != NoPos }4243// Position holds full position information for a location within an individual44// file.45type Position struct {46Filename string // Filename (if any)47Offset int // Byte offset (starting at 0)48Line int // Line number (starting at 1)49Column int // Offset from start of line (starting at 1)50}5152// Valid reports whether the position is valid. Valid positions must have a53// Line value greater than 0.54func (pos *Position) Valid() bool { return pos.Line > 0 }5556// String returns a string in one of the following forms:57//58// file:line:column Valid position with file name59// file:line Valid position with file name but no column60// line:column Valid position with no file name61// line Valid position with no file name or column62// file Invalid position with file name63// - Invalid position with no file name64func (pos Position) String() string {65s := pos.Filename6667if pos.Valid() {68if s != "" {69s += ":"70}71s += fmt.Sprintf("%d", pos.Line)72if pos.Column != 0 {73s += fmt.Sprintf(":%d", pos.Column)74}75}7677if s == "" {78s = "-"79}80return s81}8283// File holds position information for a specific file.84type File struct {85filename string86lines []int // Byte offset of each line number (first element is always 0).87}8889// NewFile creates a new File for storing position information.90func NewFile(filename string) *File {91return &File{92filename: filename,93lines: []int{0},94}95}9697// Pos returns a Pos given a byte offset. Pos panics if off is < 0.98func (f *File) Pos(off int) Pos {99if off < 0 {100panic("Pos: illegal offset")101}102return Pos{file: f, off: off}103}104105// Name returns the name of the file.106func (f *File) Name() string { return f.filename }107108// AddLine tracks a new line from a byte offset. The line offset must be larger109// than the offset for the previous line, otherwise the line offset is ignored.110func (f *File) AddLine(offset int) {111lines := len(f.lines)112if f.lines[lines-1] < offset {113f.lines = append(f.lines, offset)114}115}116117// PositionFor returns a Position from an offset.118func (f *File) PositionFor(p Pos) Position {119if p == NoPos {120return Position{}121}122123// Search through our line offsets to find the line/column info. The else124// case should never happen here, but if it does, Position.Valid will return125// false.126var line, column int127if i := searchInts(f.lines, p.off); i >= 0 {128line, column = i+1, p.off-f.lines[i]+1129}130131return Position{132Filename: f.filename,133Offset: p.off,134Line: line,135Column: column,136}137}138139func searchInts(a []int, x int) int {140return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1141}142143144