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	"strings"
19	"unicode"
20)
21
22// A MakeString is a string that may contain variable substitutions in it.
23// It can be considered as an alternating list of raw Strings and variable
24// substitutions, where the first and last entries in the list must be raw
25// Strings (possibly empty).  A MakeString that starts with a variable
26// will have an empty first raw string, and a MakeString that ends with a
27// variable will have an empty last raw string.  Two sequential Variables
28// will have an empty raw string between them.
29//
30// The MakeString is stored as two lists, a list of raw Strings and a list
31// of Variables.  The raw string list is always one longer than the variable
32// list.
33type MakeString struct {
34	StringPos Pos
35	Strings   []string
36	Variables []Variable
37}
38
39func SimpleMakeString(s string, pos Pos) *MakeString {
40	return &MakeString{
41		StringPos: pos,
42		Strings:   []string{s},
43	}
44}
45
46func (ms *MakeString) Clone() (result *MakeString) {
47	clone := *ms
48	return &clone
49}
50
51func (ms *MakeString) Pos() Pos {
52	return ms.StringPos
53}
54
55func (ms *MakeString) End() Pos {
56	pos := ms.StringPos
57	if len(ms.Strings) > 1 {
58		pos = ms.Variables[len(ms.Variables)-1].End()
59	}
60	return Pos(int(pos) + len(ms.Strings[len(ms.Strings)-1]))
61}
62
63func (ms *MakeString) appendString(s string) {
64	if len(ms.Strings) == 0 {
65		ms.Strings = []string{s}
66		return
67	} else {
68		ms.Strings[len(ms.Strings)-1] += s
69	}
70}
71
72func (ms *MakeString) appendVariable(v Variable) {
73	if len(ms.Strings) == 0 {
74		ms.Strings = []string{"", ""}
75		ms.Variables = []Variable{v}
76	} else {
77		ms.Strings = append(ms.Strings, "")
78		ms.Variables = append(ms.Variables, v)
79	}
80}
81
82func (ms *MakeString) appendMakeString(other *MakeString) {
83	last := len(ms.Strings) - 1
84	ms.Strings[last] += other.Strings[0]
85	ms.Strings = append(ms.Strings, other.Strings[1:]...)
86	ms.Variables = append(ms.Variables, other.Variables...)
87}
88
89func (ms *MakeString) Value(scope Scope) string {
90	if len(ms.Strings) == 0 {
91		return ""
92	} else {
93		ret := unescape(ms.Strings[0])
94		for i := range ms.Strings[1:] {
95			ret += ms.Variables[i].Value(scope)
96			ret += unescape(ms.Strings[i+1])
97		}
98		return ret
99	}
100}
101
102func (ms *MakeString) Dump() string {
103	if len(ms.Strings) == 0 {
104		return ""
105	} else {
106		ret := ms.Strings[0]
107		for i := range ms.Strings[1:] {
108			ret += ms.Variables[i].Dump()
109			ret += ms.Strings[i+1]
110		}
111		return ret
112	}
113}
114
115func (ms *MakeString) Const() bool {
116	return len(ms.Strings) <= 1
117}
118
119func (ms *MakeString) Empty() bool {
120	return len(ms.Strings) == 0 || (len(ms.Strings) == 1 && ms.Strings[0] == "")
121}
122
123func (ms *MakeString) Split(sep string) []*MakeString {
124	return ms.SplitN(sep, -1)
125}
126
127func (ms *MakeString) SplitN(sep string, n int) []*MakeString {
128	return ms.splitNFunc(n, func(s string, n int) []string {
129		return splitAnyN(s, sep, n)
130	})
131}
132
133func (ms *MakeString) Words() []*MakeString {
134	return ms.splitNFunc(-1, splitWords)
135}
136
137func (ms *MakeString) splitNFunc(n int, splitFunc func(s string, n int) []string) []*MakeString {
138	ret := []*MakeString{}
139
140	curMs := SimpleMakeString("", ms.Pos())
141
142	var i int
143	var s string
144	for i, s = range ms.Strings {
145		if n != 0 {
146			split := splitFunc(s, n)
147			if n != -1 {
148				if len(split) > n {
149					panic("oops!")
150				} else {
151					n -= len(split)
152				}
153			}
154			curMs.appendString(split[0])
155
156			for _, r := range split[1:] {
157				ret = append(ret, curMs)
158				curMs = SimpleMakeString(r, ms.Pos())
159			}
160		} else {
161			curMs.appendString(s)
162		}
163
164		if i < len(ms.Strings)-1 {
165			curMs.appendVariable(ms.Variables[i])
166		}
167	}
168
169	if !curMs.Empty() {
170		ret = append(ret, curMs)
171	}
172	return ret
173}
174
175func (ms *MakeString) TrimLeftSpaces() {
176	l := len(ms.Strings[0])
177	ms.Strings[0] = strings.TrimLeftFunc(ms.Strings[0], unicode.IsSpace)
178	ms.StringPos += Pos(len(ms.Strings[0]) - l)
179}
180
181func (ms *MakeString) TrimRightSpaces() {
182	last := len(ms.Strings) - 1
183	ms.Strings[last] = strings.TrimRightFunc(ms.Strings[last], unicode.IsSpace)
184}
185
186func (ms *MakeString) TrimRightOne() {
187	last := len(ms.Strings) - 1
188	if len(ms.Strings[last]) > 1 {
189		ms.Strings[last] = ms.Strings[last][0 : len(ms.Strings[last])-1]
190	}
191}
192
193func (ms *MakeString) EndsWith(ch rune) bool {
194	s := ms.Strings[len(ms.Strings)-1]
195	return s[len(s)-1] == uint8(ch)
196}
197
198func (ms *MakeString) ReplaceLiteral(input string, output string) {
199	for i := range ms.Strings {
200		ms.Strings[i] = strings.Replace(ms.Strings[i], input, output, -1)
201	}
202}
203
204func splitAnyN(s, sep string, n int) []string {
205	ret := []string{}
206	for n == -1 || n > 1 {
207		index := strings.IndexAny(s, sep)
208		if index >= 0 {
209			ret = append(ret, s[0:index])
210			s = s[index+1:]
211			if n > 0 {
212				n--
213			}
214		} else {
215			break
216		}
217	}
218	ret = append(ret, s)
219	return ret
220}
221
222func splitWords(s string, n int) []string {
223	ret := []string{}
224	preserve := ""
225	for n == -1 || n > 1 {
226		index := strings.IndexAny(s, " \t")
227		if index == 0 && len(preserve) == 0 {
228			s = s[1:]
229		} else if index >= 0 {
230			escapeCount := 0
231			for i := index - 1; i >= 0; i-- {
232				if s[i] != '\\' {
233					break
234				}
235				escapeCount += 1
236			}
237
238			if escapeCount%2 == 1 {
239				preserve += s[0 : index+1]
240				s = s[index+1:]
241				continue
242			}
243
244			ret = append(ret, preserve+s[0:index])
245			s = s[index+1:]
246			preserve = ""
247			if n > 0 {
248				n--
249			}
250		} else {
251			break
252		}
253	}
254	if preserve != "" || s != "" || len(ret) == 0 {
255		ret = append(ret, preserve+s)
256	}
257	return ret
258}
259
260func unescape(s string) string {
261	ret := ""
262	for {
263		index := strings.IndexByte(s, '\\')
264		if index < 0 {
265			break
266		}
267
268		if index+1 == len(s) {
269			break
270		}
271
272		switch s[index+1] {
273		case ' ', '\\', '#', ':', '*', '[', '|', '\t', '\n', '\r':
274			ret += s[:index] + s[index+1:index+2]
275		default:
276			ret += s[:index+2]
277		}
278		s = s[index+2:]
279	}
280	return ret + s
281}
282