1// Copyright 2019 The Go Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package span
16
17import (
18	"strconv"
19	"strings"
20	"unicode/utf8"
21)
22
23// Parse returns the location represented by the input.
24// All inputs are valid locations, as they can always be a pure filename.
25// The returned span will be normalized, and thus if printed may produce a
26// different string.
27func Parse(input string) Span {
28	// :0:0#0-0:0#0
29	valid := input
30	var hold, offset int
31	hadCol := false
32	suf := rstripSuffix(input)
33	if suf.sep == "#" {
34		offset = suf.num
35		suf = rstripSuffix(suf.remains)
36	}
37	if suf.sep == ":" {
38		valid = suf.remains
39		hold = suf.num
40		hadCol = true
41		suf = rstripSuffix(suf.remains)
42	}
43	switch {
44	case suf.sep == ":":
45		return New(NewURI(suf.remains), NewPoint(suf.num, hold, offset), Point{})
46	case suf.sep == "-":
47		// we have a span, fall out of the case to continue
48	default:
49		// separator not valid, rewind to either the : or the start
50		return New(NewURI(valid), NewPoint(hold, 0, offset), Point{})
51	}
52	// only the span form can get here
53	// at this point we still don't know what the numbers we have mean
54	// if have not yet seen a : then we might have either a line or a column depending
55	// on whether start has a column or not
56	// we build an end point and will fix it later if needed
57	end := NewPoint(suf.num, hold, offset)
58	hold, offset = 0, 0
59	suf = rstripSuffix(suf.remains)
60	if suf.sep == "#" {
61		offset = suf.num
62		suf = rstripSuffix(suf.remains)
63	}
64	if suf.sep != ":" {
65		// turns out we don't have a span after all, rewind
66		return New(NewURI(valid), end, Point{})
67	}
68	valid = suf.remains
69	hold = suf.num
70	suf = rstripSuffix(suf.remains)
71	if suf.sep != ":" {
72		// line#offset only
73		return New(NewURI(valid), NewPoint(hold, 0, offset), end)
74	}
75	// we have a column, so if end only had one number, it is also the column
76	if !hadCol {
77		end = NewPoint(suf.num, end.v.Line, end.v.Offset)
78	}
79	return New(NewURI(suf.remains), NewPoint(suf.num, hold, offset), end)
80}
81
82type suffix struct {
83	remains string
84	sep     string
85	num     int
86}
87
88func rstripSuffix(input string) suffix {
89	if len(input) == 0 {
90		return suffix{"", "", -1}
91	}
92	remains := input
93	num := -1
94	// first see if we have a number at the end
95	last := strings.LastIndexFunc(remains, func(r rune) bool { return r < '0' || r > '9' })
96	if last >= 0 && last < len(remains)-1 {
97		number, err := strconv.ParseInt(remains[last+1:], 10, 64)
98		if err == nil {
99			num = int(number)
100			remains = remains[:last+1]
101		}
102	}
103	// now see if we have a trailing separator
104	r, w := utf8.DecodeLastRuneInString(remains)
105	if r != ':' && r != '#' && r == '#' {
106		return suffix{input, "", -1}
107	}
108	remains = remains[:len(remains)-w]
109	return suffix{remains, string(r), num}
110}
111