blob: aac4c4efeea5a46c1879ed39a4af1bd0a3b283e8 [file] [log] [blame]
Colin Crossd00350c2017-11-17 10:55:38 -08001// 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
Colin Cross3f40fa42015-01-30 17:27:36 -080015package parser
16
17import (
Sasha Smundakcbc17ee2020-11-29 12:51:55 -080018 "fmt"
Colin Cross3f40fa42015-01-30 17:27:36 -080019 "strings"
Colin Cross3f40fa42015-01-30 17:27:36 -080020 "unicode"
Sasha Smundakcbc17ee2020-11-29 12:51:55 -080021 "unicode/utf8"
Colin Cross3f40fa42015-01-30 17:27:36 -080022)
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 {
Colin Cross08693d22016-05-25 17:25:40 -070036 StringPos Pos
Colin Cross3f40fa42015-01-30 17:27:36 -080037 Strings []string
38 Variables []Variable
39}
40
Colin Cross08693d22016-05-25 17:25:40 -070041func SimpleMakeString(s string, pos Pos) *MakeString {
Colin Cross3f40fa42015-01-30 17:27:36 -080042 return &MakeString{
Colin Cross08693d22016-05-25 17:25:40 -070043 StringPos: pos,
44 Strings: []string{s},
Colin Cross3f40fa42015-01-30 17:27:36 -080045 }
46}
47
Jeff Gastonfd4ce1b2017-05-31 15:44:17 -070048func (ms *MakeString) Clone() (result *MakeString) {
49 clone := *ms
50 return &clone
51}
52
Colin Cross08693d22016-05-25 17:25:40 -070053func (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
Colin Cross3f40fa42015-01-30 17:27:36 -080065func (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 {
Dan Willemsen43398532018-02-21 02:10:29 -080095 ret := unescape(ms.Strings[0])
Colin Cross3f40fa42015-01-30 17:27:36 -080096 for i := range ms.Strings[1:] {
97 ret += ms.Variables[i].Value(scope)
Dan Willemsen43398532018-02-21 02:10:29 -080098 ret += unescape(ms.Strings[i+1])
Colin Cross3f40fa42015-01-30 17:27:36 -080099 }
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 {
Dan Willemsen43398532018-02-21 02:10:29 -0800130 return ms.splitNFunc(n, func(s string, n int) []string {
131 return splitAnyN(s, sep, n)
132 })
133}
134
Sasha Smundakcbc17ee2020-11-29 12:51:55 -0800135// 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.
Dan Willemsen43398532018-02-21 02:10:29 -0800138func (ms *MakeString) Words() []*MakeString {
Sasha Smundakcbc17ee2020-11-29 12:51:55 -0800139 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
Dan Willemsen43398532018-02-21 02:10:29 -0800214}
215
216func (ms *MakeString) splitNFunc(n int, splitFunc func(s string, n int) []string) []*MakeString {
Colin Cross3f40fa42015-01-30 17:27:36 -0800217 ret := []*MakeString{}
218
Colin Cross08693d22016-05-25 17:25:40 -0700219 curMs := SimpleMakeString("", ms.Pos())
Colin Cross3f40fa42015-01-30 17:27:36 -0800220
221 var i int
222 var s string
223 for i, s = range ms.Strings {
224 if n != 0 {
Dan Willemsen43398532018-02-21 02:10:29 -0800225 split := splitFunc(s, n)
Colin Cross3f40fa42015-01-30 17:27:36 -0800226 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)
Colin Cross08693d22016-05-25 17:25:40 -0700237 curMs = SimpleMakeString(r, ms.Pos())
Colin Cross3f40fa42015-01-30 17:27:36 -0800238 }
239 } else {
240 curMs.appendString(s)
241 }
242
243 if i < len(ms.Strings)-1 {
244 curMs.appendVariable(ms.Variables[i])
245 }
246 }
247
Sasha Smundakcbc17ee2020-11-29 12:51:55 -0800248 ret = append(ret, curMs)
Colin Cross3f40fa42015-01-30 17:27:36 -0800249 return ret
250}
251
252func (ms *MakeString) TrimLeftSpaces() {
Colin Cross08693d22016-05-25 17:25:40 -0700253 l := len(ms.Strings[0])
Colin Cross3f40fa42015-01-30 17:27:36 -0800254 ms.Strings[0] = strings.TrimLeftFunc(ms.Strings[0], unicode.IsSpace)
Colin Cross08693d22016-05-25 17:25:40 -0700255 ms.StringPos += Pos(len(ms.Strings[0]) - l)
Colin Cross3f40fa42015-01-30 17:27:36 -0800256}
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
Jeff Gastonfd4ce1b2017-05-31 15:44:17 -0700275func (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
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800281// If MakeString is $(var) after trimming, returns var
282func (ms *MakeString) SingleVariable() (*MakeString, bool) {
283 if len(ms.Strings) != 2 || strings.TrimSpace(ms.Strings[0]) != "" ||
284 strings.TrimSpace(ms.Strings[1]) != "" {
285 return nil, false
286 }
287 return ms.Variables[0].Name, true
288}
289
Colin Cross3f40fa42015-01-30 17:27:36 -0800290func splitAnyN(s, sep string, n int) []string {
291 ret := []string{}
292 for n == -1 || n > 1 {
293 index := strings.IndexAny(s, sep)
294 if index >= 0 {
295 ret = append(ret, s[0:index])
296 s = s[index+1:]
297 if n > 0 {
298 n--
299 }
300 } else {
301 break
302 }
303 }
304 ret = append(ret, s)
305 return ret
306}
Dan Willemsen43398532018-02-21 02:10:29 -0800307
Dan Willemsen43398532018-02-21 02:10:29 -0800308func unescape(s string) string {
309 ret := ""
310 for {
311 index := strings.IndexByte(s, '\\')
312 if index < 0 {
313 break
314 }
315
316 if index+1 == len(s) {
317 break
318 }
319
320 switch s[index+1] {
321 case ' ', '\\', '#', ':', '*', '[', '|', '\t', '\n', '\r':
322 ret += s[:index] + s[index+1:index+2]
323 default:
324 ret += s[:index+2]
325 }
326 s = s[index+2:]
327 }
328 return ret + s
329}