| // Copyright 2019 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 span |
| |
| import ( |
| "strconv" |
| "strings" |
| "unicode/utf8" |
| ) |
| |
| // Parse returns the location represented by the input. |
| // Only file paths are accepted, not URIs. |
| // The returned span will be normalized, and thus if printed may produce a |
| // different string. |
| func Parse(input string) Span { |
| // :0:0#0-0:0#0 |
| valid := input |
| var hold, offset int |
| hadCol := false |
| suf := rstripSuffix(input) |
| if suf.sep == "#" { |
| offset = suf.num |
| suf = rstripSuffix(suf.remains) |
| } |
| if suf.sep == ":" { |
| valid = suf.remains |
| hold = suf.num |
| hadCol = true |
| suf = rstripSuffix(suf.remains) |
| } |
| switch { |
| case suf.sep == ":": |
| return New(URIFromPath(suf.remains), NewPoint(suf.num, hold, offset), Point{}) |
| case suf.sep == "-": |
| // we have a span, fall out of the case to continue |
| default: |
| // separator not valid, rewind to either the : or the start |
| return New(URIFromPath(valid), NewPoint(hold, 0, offset), Point{}) |
| } |
| // only the span form can get here |
| // at this point we still don't know what the numbers we have mean |
| // if have not yet seen a : then we might have either a line or a column depending |
| // on whether start has a column or not |
| // we build an end point and will fix it later if needed |
| end := NewPoint(suf.num, hold, offset) |
| hold, offset = 0, 0 |
| suf = rstripSuffix(suf.remains) |
| if suf.sep == "#" { |
| offset = suf.num |
| suf = rstripSuffix(suf.remains) |
| } |
| if suf.sep != ":" { |
| // turns out we don't have a span after all, rewind |
| return New(URIFromPath(valid), end, Point{}) |
| } |
| valid = suf.remains |
| hold = suf.num |
| suf = rstripSuffix(suf.remains) |
| if suf.sep != ":" { |
| // line#offset only |
| return New(URIFromPath(valid), NewPoint(hold, 0, offset), end) |
| } |
| // we have a column, so if end only had one number, it is also the column |
| if !hadCol { |
| end = NewPoint(suf.num, end.v.Line, end.v.Offset) |
| } |
| return New(URIFromPath(suf.remains), NewPoint(suf.num, hold, offset), end) |
| } |
| |
| type suffix struct { |
| remains string |
| sep string |
| num int |
| } |
| |
| func rstripSuffix(input string) suffix { |
| if len(input) == 0 { |
| return suffix{"", "", -1} |
| } |
| remains := input |
| num := -1 |
| // first see if we have a number at the end |
| last := strings.LastIndexFunc(remains, func(r rune) bool { return r < '0' || r > '9' }) |
| if last >= 0 && last < len(remains)-1 { |
| number, err := strconv.ParseInt(remains[last+1:], 10, 64) |
| if err == nil { |
| num = int(number) |
| remains = remains[:last+1] |
| } |
| } |
| // now see if we have a trailing separator |
| r, w := utf8.DecodeLastRuneInString(remains) |
| if r != ':' && r != '#' && r == '#' { |
| return suffix{input, "", -1} |
| } |
| remains = remains[:len(remains)-w] |
| return suffix{remains, string(r), num} |
| } |