1// Copyright 2017 Google Inc. All rights reserved.
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 parser
16
17import (
18	"fmt"
19	"strings"
20	"unicode"
21	"unicode/utf8"
22)
23
24// A MakeString is a string that may contain variable substitutions in it.
25// It can be considered as an alternating list of raw Strings and variable
26// substitutions, where the first and last entries in the list must be raw
27// Strings (possibly empty).  A MakeString that starts with a variable
28// will have an empty first raw string, and a MakeString that ends with a
29// variable will have an empty last raw string.  Two sequential Variables
30// will have an empty raw string between them.
31//
32// The MakeString is stored as two lists, a list of raw Strings and a list
33// of Variables.  The raw string list is always one longer than the variable
34// list.
35type MakeString struct {
36	StringPos Pos
37	Strings   []string
38	Variables []Variable
39}
40
41func SimpleMakeString(s string, pos Pos) *MakeString {
42	return &MakeString{
43		StringPos: pos,
44		Strings:   []string{s},
45	}
46}
47
48func (ms *MakeString) Clone() (result *MakeString) {
49	clone := *ms
50	return &clone
51}
52
53func (ms *MakeString) Pos() Pos {
54	return ms.StringPos
55}
56
57func (ms *MakeString) End() Pos {
58	pos := ms.StringPos
59	if len(ms.Strings) > 1 {
60		pos = ms.Variables[len(ms.Variables)-1].End()
61	}
62	return Pos(int(pos) + len(ms.Strings[len(ms.Strings)-1]))
63}
64
65func (ms *MakeString) appendString(s string) {
66	if len(ms.Strings) == 0 {
67		ms.Strings = []string{s}
68		return
69	} else {
70		ms.Strings[len(ms.Strings)-1] += s
71	}
72}
73
74func (ms *MakeString) appendVariable(v Variable) {
75	if len(ms.Strings) == 0 {
76		ms.Strings = []string{"", ""}
77		ms.Variables = []Variable{v}
78	} else {
79		ms.Strings = append(ms.Strings, "")
80		ms.Variables = append(ms.Variables, v)
81	}
82}
83
84func (ms *MakeString) appendMakeString(other *MakeString) {
85	last := len(ms.Strings) - 1
86	ms.Strings[last] += other.Strings[0]
87	ms.Strings = append(ms.Strings, other.Strings[1:]...)
88	ms.Variables = append(ms.Variables, other.Variables...)
89}
90
91func (ms *MakeString) Value(scope Scope) string {
92	if len(ms.Strings) == 0 {
93		return ""
94	} else {
95		ret := unescape(ms.Strings[0])
96		for i := range ms.Strings[1:] {
97			ret += ms.Variables[i].Value(scope)
98			ret += unescape(ms.Strings[i+1])
99		}
100		return ret
101	}
102}
103
104func (ms *MakeString) Dump() string {
105	if len(ms.Strings) == 0 {
106		return ""
107	} else {
108		ret := ms.Strings[0]
109		for i := range ms.Strings[1:] {
110			ret += ms.Variables[i].Dump()
111			ret += ms.Strings[i+1]
112		}
113		return ret
114	}
115}
116
117func (ms *MakeString) Const() bool {
118	return len(ms.Strings) <= 1
119}
120
121func (ms *MakeString) Empty() bool {
122	return len(ms.Strings) == 0 || (len(ms.Strings) == 1 && ms.Strings[0] == "")
123}
124
125func (ms *MakeString) Split(sep string) []*MakeString {
126	return ms.SplitN(sep, -1)
127}
128
129func (ms *MakeString) SplitN(sep string, n int) []*MakeString {
130	return ms.splitNFunc(n, func(s string, n int) []string {
131		return splitAnyN(s, sep, n)
132	})
133}
134
135// Words splits MakeString into multiple makeStrings separated by whitespace.
136// Thus, " a $(X)b  c " will be split into ["a", "$(X)b", "c"].
137// Splitting a MakeString consisting solely of whitespace yields empty array.
138func (ms *MakeString) Words() []*MakeString {
139	var ch rune    // current character
140	const EOF = -1 // no more characters
141	const EOS = -2 // at the end of a string chunk
142
143	// Next character's chunk and position
144	iString := 0
145	iChar := 0
146
147	var words []*MakeString
148	word := SimpleMakeString("", ms.Pos())
149
150	nextChar := func() {
151		if iString >= len(ms.Strings) {
152			ch = EOF
153		} else if iChar >= len(ms.Strings[iString]) {
154			iString++
155			iChar = 0
156			ch = EOS
157		} else {
158			var w int
159			ch, w = utf8.DecodeRuneInString(ms.Strings[iString][iChar:])
160			iChar += w
161		}
162	}
163
164	appendVariableAndAdvance := func() {
165		if iString-1 < len(ms.Variables) {
166			word.appendVariable(ms.Variables[iString-1])
167		}
168		nextChar()
169	}
170
171	appendCharAndAdvance := func(c rune) {
172		if c != EOF {
173			word.appendString(string(c))
174		}
175		nextChar()
176	}
177
178	nextChar()
179	for ch != EOF {
180		// Skip whitespace
181		for ch == ' ' || ch == '\t' {
182			nextChar()
183		}
184		if ch == EOS {
185			// "... $(X)... " case. The current word should be empty.
186			if !word.Empty() {
187				panic(fmt.Errorf("%q: EOS while current word %q is not empty, iString=%d",
188					ms.Dump(), word.Dump(), iString))
189			}
190			appendVariableAndAdvance()
191		}
192		// Copy word
193		for ch != EOF {
194			if ch == ' ' || ch == '\t' {
195				words = append(words, word)
196				word = SimpleMakeString("", ms.Pos())
197				break
198			}
199			if ch == EOS {
200				// "...a$(X)..." case. Append variable to the current word
201				appendVariableAndAdvance()
202			} else {
203				if ch == '\\' {
204					appendCharAndAdvance('\\')
205				}
206				appendCharAndAdvance(ch)
207			}
208		}
209	}
210	if !word.Empty() {
211		words = append(words, word)
212	}
213	return words
214}
215
216func (ms *MakeString) splitNFunc(n int, splitFunc func(s string, n int) []string) []*MakeString {
217	ret := []*MakeString{}
218
219	curMs := SimpleMakeString("", ms.Pos())
220
221	var i int
222	var s string
223	for i, s = range ms.Strings {
224		if n != 0 {
225			split := splitFunc(s, n)
226			if n != -1 {
227				if len(split) > n {
228					panic("oops!")
229				} else {
230					n -= len(split)
231				}
232			}
233			curMs.appendString(split[0])
234
235			for _, r := range split[1:] {
236				ret = append(ret, curMs)
237				curMs = SimpleMakeString(r, ms.Pos())
238			}
239		} else {
240			curMs.appendString(s)
241		}
242
243		if i < len(ms.Strings)-1 {
244			curMs.appendVariable(ms.Variables[i])
245		}
246	}
247
248	ret = append(ret, curMs)
249	return ret
250}
251
252func (ms *MakeString) TrimLeftSpaces() {
253	l := len(ms.Strings[0])
254	ms.Strings[0] = strings.TrimLeftFunc(ms.Strings[0], unicode.IsSpace)
255	ms.StringPos += Pos(len(ms.Strings[0]) - l)
256}
257
258func (ms *MakeString) TrimRightSpaces() {
259	last := len(ms.Strings) - 1
260	ms.Strings[last] = strings.TrimRightFunc(ms.Strings[last], unicode.IsSpace)
261}
262
263func (ms *MakeString) TrimRightOne() {
264	last := len(ms.Strings) - 1
265	if len(ms.Strings[last]) > 1 {
266		ms.Strings[last] = ms.Strings[last][0 : len(ms.Strings[last])-1]
267	}
268}
269
270func (ms *MakeString) EndsWith(ch rune) bool {
271	s := ms.Strings[len(ms.Strings)-1]
272	return s[len(s)-1] == uint8(ch)
273}
274
275func (ms *MakeString) ReplaceLiteral(input string, output string) {
276	for i := range ms.Strings {
277		ms.Strings[i] = strings.Replace(ms.Strings[i], input, output, -1)
278	}
279}
280
281func splitAnyN(s, sep string, n int) []string {
282	ret := []string{}
283	for n == -1 || n > 1 {
284		index := strings.IndexAny(s, sep)
285		if index >= 0 {
286			ret = append(ret, s[0:index])
287			s = s[index+1:]
288			if n > 0 {
289				n--
290			}
291		} else {
292			break
293		}
294	}
295	ret = append(ret, s)
296	return ret
297}
298
299func unescape(s string) string {
300	ret := ""
301	for {
302		index := strings.IndexByte(s, '\\')
303		if index < 0 {
304			break
305		}
306
307		if index+1 == len(s) {
308			break
309		}
310
311		switch s[index+1] {
312		case ' ', '\\', '#', ':', '*', '[', '|', '\t', '\n', '\r':
313			ret += s[:index] + s[index+1:index+2]
314		default:
315			ret += s[:index+2]
316		}
317		s = s[index+2:]
318	}
319	return ret + s
320}
321