123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- package parser
- import (
- "fmt"
- "sort"
- )
- // SourceFilePos represents a position information in the file.
- type SourceFilePos struct {
- Filename string // filename, if any
- Offset int // offset, starting at 0
- Line int // line number, starting at 1
- Column int // column number, starting at 1 (byte count)
- }
- // IsValid returns true if the position is valid.
- func (p SourceFilePos) IsValid() bool {
- return p.Line > 0
- }
- // String returns a string in one of several forms:
- //
- // file:line:column valid position with file name
- // file:line valid position with file name but no column (column == 0)
- // line:column valid position without file name
- // line valid position without file name and no column (column == 0)
- // file invalid position with file name
- // - invalid position without file name
- //
- func (p SourceFilePos) String() string {
- s := p.Filename
- if p.IsValid() {
- if s != "" {
- s += ":"
- }
- s += fmt.Sprintf("%d", p.Line)
- if p.Column != 0 {
- s += fmt.Sprintf(":%d", p.Column)
- }
- }
- if s == "" {
- s = "-"
- }
- return s
- }
- // SourceFileSet represents a set of source files.
- type SourceFileSet struct {
- Base int // base offset for the next file
- Files []*SourceFile // list of files in the order added to the set
- LastFile *SourceFile // cache of last file looked up
- }
- // NewFileSet creates a new file set.
- func NewFileSet() *SourceFileSet {
- return &SourceFileSet{
- Base: 1, // 0 == NoPos
- }
- }
- // AddFile adds a new file in the file set.
- func (s *SourceFileSet) AddFile(filename string, base, size int) *SourceFile {
- if base < 0 {
- base = s.Base
- }
- if base < s.Base || size < 0 {
- panic("illegal base or size")
- }
- f := &SourceFile{
- set: s,
- Name: filename,
- Base: base,
- Size: size,
- Lines: []int{0},
- }
- base += size + 1 // +1 because EOF also has a position
- if base < 0 {
- panic("offset overflow (> 2G of source code in file set)")
- }
- // add the file to the file set
- s.Base = base
- s.Files = append(s.Files, f)
- s.LastFile = f
- return f
- }
- // File returns the file that contains the position p. If no such file is
- // found (for instance for p == NoPos), the result is nil.
- func (s *SourceFileSet) File(p Pos) (f *SourceFile) {
- if p != NoPos {
- f = s.file(p)
- }
- return
- }
- // Position converts a SourcePos p in the fileset into a SourceFilePos value.
- func (s *SourceFileSet) Position(p Pos) (pos SourceFilePos) {
- if p != NoPos {
- if f := s.file(p); f != nil {
- return f.position(p)
- }
- }
- return
- }
- func (s *SourceFileSet) file(p Pos) *SourceFile {
- // common case: p is in last file
- f := s.LastFile
- if f != nil && f.Base <= int(p) && int(p) <= f.Base+f.Size {
- return f
- }
- // p is not in last file - search all files
- if i := searchFiles(s.Files, int(p)); i >= 0 {
- f := s.Files[i]
- // f.base <= int(p) by definition of searchFiles
- if int(p) <= f.Base+f.Size {
- s.LastFile = f // race is ok - s.last is only a cache
- return f
- }
- }
- return nil
- }
- func searchFiles(a []*SourceFile, x int) int {
- return sort.Search(len(a), func(i int) bool { return a[i].Base > x }) - 1
- }
- // SourceFile represents a source file.
- type SourceFile struct {
- // SourceFile set for the file
- set *SourceFileSet
- // SourceFile name as provided to AddFile
- Name string
- // SourcePos value range for this file is [base...base+size]
- Base int
- // SourceFile size as provided to AddFile
- Size int
- // Lines contains the offset of the first character for each line
- // (the first entry is always 0)
- Lines []int
- }
- // Set returns SourceFileSet.
- func (f *SourceFile) Set() *SourceFileSet {
- return f.set
- }
- // LineCount returns the current number of lines.
- func (f *SourceFile) LineCount() int {
- return len(f.Lines)
- }
- // AddLine adds a new line.
- func (f *SourceFile) AddLine(offset int) {
- i := len(f.Lines)
- if (i == 0 || f.Lines[i-1] < offset) && offset < f.Size {
- f.Lines = append(f.Lines, offset)
- }
- }
- // LineStart returns the position of the first character in the line.
- func (f *SourceFile) LineStart(line int) Pos {
- if line < 1 {
- panic("illegal line number (line numbering starts at 1)")
- }
- if line > len(f.Lines) {
- panic("illegal line number")
- }
- return Pos(f.Base + f.Lines[line-1])
- }
- // FileSetPos returns the position in the file set.
- func (f *SourceFile) FileSetPos(offset int) Pos {
- if offset > f.Size {
- panic("illegal file offset")
- }
- return Pos(f.Base + offset)
- }
- // Offset translates the file set position into the file offset.
- func (f *SourceFile) Offset(p Pos) int {
- if int(p) < f.Base || int(p) > f.Base+f.Size {
- panic("illegal SourcePos value")
- }
- return int(p) - f.Base
- }
- // Position translates the file set position into the file position.
- func (f *SourceFile) Position(p Pos) (pos SourceFilePos) {
- if p != NoPos {
- if int(p) < f.Base || int(p) > f.Base+f.Size {
- panic("illegal SourcePos value")
- }
- pos = f.position(p)
- }
- return
- }
- func (f *SourceFile) position(p Pos) (pos SourceFilePos) {
- offset := int(p) - f.Base
- pos.Offset = offset
- pos.Filename, pos.Line, pos.Column = f.unpack(offset)
- return
- }
- func (f *SourceFile) unpack(offset int) (filename string, line, column int) {
- filename = f.Name
- if i := searchInts(f.Lines, offset); i >= 0 {
- line, column = i+1, offset-f.Lines[i]+1
- }
- return
- }
- func searchInts(a []int, x int) int {
- // This function body is a manually inlined version of:
- // return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1
- i, j := 0, len(a)
- for i < j {
- h := i + (j-i)/2 // avoid overflow when computing h
- // i ≤ h < j
- if a[h] <= x {
- i = h + 1
- } else {
- j = h
- }
- }
- return i - 1
- }
|