// Package file encapsulates the file abstractions used by the ast & parser.
package file import ( ) // Idx is a compact encoding of a source position within a file set. // It can be converted into a Position for a more convenient, but much // larger, representation. type Idx int // Position describes an arbitrary source position // including the filename, line, and column location. type Position struct { Filename string // The filename where the error occurred, if any Line int // The line number, starting at 1 Column int // The column number, starting at 1 (The character count) } // A Position is valid if the line number is > 0. func ( *Position) () bool { return .Line > 0 } // String returns a string in one of several forms: // // file:line:column A valid position with filename // line:column A valid position without filename // file An invalid position with filename // - An invalid position without filename func ( Position) () string { := .Filename if .isValid() { if != "" { += ":" } += fmt.Sprintf("%d:%d", .Line, .Column) } if == "" { = "-" } return } // FileSet // A FileSet represents a set of source files. type FileSet struct { files []*File last *File } // AddFile adds a new file with the given filename and src. // // This an internal method, but exported for cross-package use. func ( *FileSet) (, string) int { := .nextBase() := &File{ name: , src: , base: , } .files = append(.files, ) .last = return } func ( *FileSet) () int { if .last == nil { return 1 } return .last.base + len(.last.src) + 1 } func ( *FileSet) ( Idx) *File { for , := range .files { if <= Idx(.base+len(.src)) { return } } return nil } // Position converts an Idx in the FileSet into a Position. func ( *FileSet) ( Idx) Position { for , := range .files { if <= Idx(.base+len(.src)) { return .Position(int() - .base) } } return Position{} } type File struct { mu sync.Mutex name string src string base int // This will always be 1 or greater sourceMap *sourcemap.Consumer lineOffsets []int lastScannedOffset int } func (, string, int) *File { return &File{ name: , src: , base: , } } func ( *File) () string { return .name } func ( *File) () string { return .src } func ( *File) () int { return .base } func ( *File) ( *sourcemap.Consumer) { .sourceMap = } func ( *File) ( int) Position { var int var []int .mu.Lock() if > .lastScannedOffset { = .scanTo() = .lineOffsets .mu.Unlock() } else { = .lineOffsets .mu.Unlock() = sort.Search(len(), func( int) bool { return [] > }) - 1 } var int if >= 0 { = [] } := + 2 := - + 1 if .sourceMap != nil { if , , , , := .sourceMap.Source(, ); { := := ResolveSourcemapURL(.Name(), ) if != nil { = .String() } return Position{ Filename: , Line: , Column: , } } } return Position{ Filename: .name, Line: , Column: , } } func (, string) *url.URL { // if the url is absolute(has scheme) there is nothing to do , := url.Parse(strings.TrimSpace()) if == nil && !.IsAbs() { , := url.Parse(strings.TrimSpace()) if == nil && path.IsAbs(.Path) { = .ResolveReference() } else { // pathological case where both are not absolute paths and using Resolve // as above will produce an absolute one , _ = url.Parse(path.Join(path.Dir(), .Path)) } } return } func findNextLineStart( string) int { for , := range { switch { case '\r': if < len()-1 && [+1] == '\n' { return + 2 } return + 1 case '\n': return + 1 case '\u2028', '\u2029': return + 3 } } return -1 } func ( *File) ( int) int { := .lastScannedOffset for < { := findNextLineStart(.src[:]) if == -1 { .lastScannedOffset = len(.src) return len(.lineOffsets) - 1 } = + .lineOffsets = append(.lineOffsets, ) } .lastScannedOffset = if == { return len(.lineOffsets) - 1 } return len(.lineOffsets) - 2 }