blob: 803032649bcb84e0ca5e1394ab57146e95515af5 [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
Trevor Radcliffeecdb9f72022-02-28 20:38:35 +000027// Strings (possibly empty). The entirety of the text before the first variable,
28// between two variables, and after the last variable will be considered a
29// single String value. A MakeString that starts with a variable will have an
30// empty first raw string, and a MakeString that ends with a variable will have
31// an empty last raw string. Two sequential Variables will have an empty raw
32// string between them.
Colin Cross3f40fa42015-01-30 17:27:36 -080033//
34// The MakeString is stored as two lists, a list of raw Strings and a list
35// of Variables. The raw string list is always one longer than the variable
36// list.
Trevor Radcliffeecdb9f72022-02-28 20:38:35 +000037//
38// For example, "$(FOO)/bar/baz" will be represented as the
39// following lists:
40//
41// {
42// Strings: ["", "/bar/baz"],
43// Variables: ["FOO"]
44// }
Colin Cross3f40fa42015-01-30 17:27:36 -080045type MakeString struct {
Colin Cross08693d22016-05-25 17:25:40 -070046 StringPos Pos
Colin Cross3f40fa42015-01-30 17:27:36 -080047 Strings []string
48 Variables []Variable
49}
50
Colin Cross08693d22016-05-25 17:25:40 -070051func SimpleMakeString(s string, pos Pos) *MakeString {
Colin Cross3f40fa42015-01-30 17:27:36 -080052 return &MakeString{
Colin Cross08693d22016-05-25 17:25:40 -070053 StringPos: pos,
54 Strings: []string{s},
Colin Cross3f40fa42015-01-30 17:27:36 -080055 }
56}
57
Jeff Gastonfd4ce1b2017-05-31 15:44:17 -070058func (ms *MakeString) Clone() (result *MakeString) {
59 clone := *ms
60 return &clone
61}
62
Colin Cross08693d22016-05-25 17:25:40 -070063func (ms *MakeString) Pos() Pos {
64 return ms.StringPos
65}
66
67func (ms *MakeString) End() Pos {
68 pos := ms.StringPos
69 if len(ms.Strings) > 1 {
70 pos = ms.Variables[len(ms.Variables)-1].End()
71 }
72 return Pos(int(pos) + len(ms.Strings[len(ms.Strings)-1]))
73}
74
Colin Cross3f40fa42015-01-30 17:27:36 -080075func (ms *MakeString) appendString(s string) {
76 if len(ms.Strings) == 0 {
77 ms.Strings = []string{s}
78 return
79 } else {
80 ms.Strings[len(ms.Strings)-1] += s
81 }
82}
83
84func (ms *MakeString) appendVariable(v Variable) {
85 if len(ms.Strings) == 0 {
86 ms.Strings = []string{"", ""}
87 ms.Variables = []Variable{v}
88 } else {
89 ms.Strings = append(ms.Strings, "")
90 ms.Variables = append(ms.Variables, v)
91 }
92}
93
94func (ms *MakeString) appendMakeString(other *MakeString) {
95 last := len(ms.Strings) - 1
96 ms.Strings[last] += other.Strings[0]
97 ms.Strings = append(ms.Strings, other.Strings[1:]...)
98 ms.Variables = append(ms.Variables, other.Variables...)
99}
100
101func (ms *MakeString) Value(scope Scope) string {
102 if len(ms.Strings) == 0 {
103 return ""
104 } else {
Dan Willemsen43398532018-02-21 02:10:29 -0800105 ret := unescape(ms.Strings[0])
Colin Cross3f40fa42015-01-30 17:27:36 -0800106 for i := range ms.Strings[1:] {
107 ret += ms.Variables[i].Value(scope)
Dan Willemsen43398532018-02-21 02:10:29 -0800108 ret += unescape(ms.Strings[i+1])
Colin Cross3f40fa42015-01-30 17:27:36 -0800109 }
110 return ret
111 }
112}
113
114func (ms *MakeString) Dump() string {
115 if len(ms.Strings) == 0 {
116 return ""
117 } else {
118 ret := ms.Strings[0]
119 for i := range ms.Strings[1:] {
120 ret += ms.Variables[i].Dump()
121 ret += ms.Strings[i+1]
122 }
123 return ret
124 }
125}
126
127func (ms *MakeString) Const() bool {
128 return len(ms.Strings) <= 1
129}
130
131func (ms *MakeString) Empty() bool {
132 return len(ms.Strings) == 0 || (len(ms.Strings) == 1 && ms.Strings[0] == "")
133}
134
135func (ms *MakeString) Split(sep string) []*MakeString {
136 return ms.SplitN(sep, -1)
137}
138
139func (ms *MakeString) SplitN(sep string, n int) []*MakeString {
Dan Willemsen43398532018-02-21 02:10:29 -0800140 return ms.splitNFunc(n, func(s string, n int) []string {
141 return splitAnyN(s, sep, n)
142 })
143}
144
Sasha Smundakcbc17ee2020-11-29 12:51:55 -0800145// Words splits MakeString into multiple makeStrings separated by whitespace.
146// Thus, " a $(X)b c " will be split into ["a", "$(X)b", "c"].
147// Splitting a MakeString consisting solely of whitespace yields empty array.
Dan Willemsen43398532018-02-21 02:10:29 -0800148func (ms *MakeString) Words() []*MakeString {
Sasha Smundakcbc17ee2020-11-29 12:51:55 -0800149 var ch rune // current character
150 const EOF = -1 // no more characters
151 const EOS = -2 // at the end of a string chunk
152
153 // Next character's chunk and position
154 iString := 0
155 iChar := 0
156
157 var words []*MakeString
158 word := SimpleMakeString("", ms.Pos())
159
160 nextChar := func() {
161 if iString >= len(ms.Strings) {
162 ch = EOF
163 } else if iChar >= len(ms.Strings[iString]) {
164 iString++
165 iChar = 0
166 ch = EOS
167 } else {
168 var w int
169 ch, w = utf8.DecodeRuneInString(ms.Strings[iString][iChar:])
170 iChar += w
171 }
172 }
173
174 appendVariableAndAdvance := func() {
175 if iString-1 < len(ms.Variables) {
176 word.appendVariable(ms.Variables[iString-1])
177 }
178 nextChar()
179 }
180
181 appendCharAndAdvance := func(c rune) {
182 if c != EOF {
183 word.appendString(string(c))
184 }
185 nextChar()
186 }
187
188 nextChar()
189 for ch != EOF {
190 // Skip whitespace
191 for ch == ' ' || ch == '\t' {
192 nextChar()
193 }
194 if ch == EOS {
195 // "... $(X)... " case. The current word should be empty.
196 if !word.Empty() {
197 panic(fmt.Errorf("%q: EOS while current word %q is not empty, iString=%d",
198 ms.Dump(), word.Dump(), iString))
199 }
200 appendVariableAndAdvance()
201 }
202 // Copy word
203 for ch != EOF {
204 if ch == ' ' || ch == '\t' {
205 words = append(words, word)
206 word = SimpleMakeString("", ms.Pos())
207 break
208 }
209 if ch == EOS {
210 // "...a$(X)..." case. Append variable to the current word
211 appendVariableAndAdvance()
212 } else {
213 if ch == '\\' {
214 appendCharAndAdvance('\\')
215 }
216 appendCharAndAdvance(ch)
217 }
218 }
219 }
220 if !word.Empty() {
221 words = append(words, word)
222 }
223 return words
Dan Willemsen43398532018-02-21 02:10:29 -0800224}
225
226func (ms *MakeString) splitNFunc(n int, splitFunc func(s string, n int) []string) []*MakeString {
Colin Cross3f40fa42015-01-30 17:27:36 -0800227 ret := []*MakeString{}
228
Colin Cross08693d22016-05-25 17:25:40 -0700229 curMs := SimpleMakeString("", ms.Pos())
Colin Cross3f40fa42015-01-30 17:27:36 -0800230
231 var i int
232 var s string
233 for i, s = range ms.Strings {
234 if n != 0 {
Dan Willemsen43398532018-02-21 02:10:29 -0800235 split := splitFunc(s, n)
Colin Cross3f40fa42015-01-30 17:27:36 -0800236 if n != -1 {
237 if len(split) > n {
238 panic("oops!")
239 } else {
240 n -= len(split)
241 }
242 }
243 curMs.appendString(split[0])
244
245 for _, r := range split[1:] {
246 ret = append(ret, curMs)
Colin Cross08693d22016-05-25 17:25:40 -0700247 curMs = SimpleMakeString(r, ms.Pos())
Colin Cross3f40fa42015-01-30 17:27:36 -0800248 }
249 } else {
250 curMs.appendString(s)
251 }
252
253 if i < len(ms.Strings)-1 {
254 curMs.appendVariable(ms.Variables[i])
255 }
256 }
257
Sasha Smundakcbc17ee2020-11-29 12:51:55 -0800258 ret = append(ret, curMs)
Colin Cross3f40fa42015-01-30 17:27:36 -0800259 return ret
260}
261
262func (ms *MakeString) TrimLeftSpaces() {
Colin Cross08693d22016-05-25 17:25:40 -0700263 l := len(ms.Strings[0])
Colin Cross3f40fa42015-01-30 17:27:36 -0800264 ms.Strings[0] = strings.TrimLeftFunc(ms.Strings[0], unicode.IsSpace)
Colin Cross08693d22016-05-25 17:25:40 -0700265 ms.StringPos += Pos(len(ms.Strings[0]) - l)
Colin Cross3f40fa42015-01-30 17:27:36 -0800266}
267
268func (ms *MakeString) TrimRightSpaces() {
269 last := len(ms.Strings) - 1
270 ms.Strings[last] = strings.TrimRightFunc(ms.Strings[last], unicode.IsSpace)
271}
272
273func (ms *MakeString) TrimRightOne() {
274 last := len(ms.Strings) - 1
275 if len(ms.Strings[last]) > 1 {
276 ms.Strings[last] = ms.Strings[last][0 : len(ms.Strings[last])-1]
277 }
278}
279
280func (ms *MakeString) EndsWith(ch rune) bool {
281 s := ms.Strings[len(ms.Strings)-1]
282 return s[len(s)-1] == uint8(ch)
283}
284
Jeff Gastonfd4ce1b2017-05-31 15:44:17 -0700285func (ms *MakeString) ReplaceLiteral(input string, output string) {
286 for i := range ms.Strings {
287 ms.Strings[i] = strings.Replace(ms.Strings[i], input, output, -1)
288 }
289}
290
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800291// If MakeString is $(var) after trimming, returns var
292func (ms *MakeString) SingleVariable() (*MakeString, bool) {
293 if len(ms.Strings) != 2 || strings.TrimSpace(ms.Strings[0]) != "" ||
294 strings.TrimSpace(ms.Strings[1]) != "" {
295 return nil, false
296 }
297 return ms.Variables[0].Name, true
298}
299
Colin Cross3f40fa42015-01-30 17:27:36 -0800300func splitAnyN(s, sep string, n int) []string {
301 ret := []string{}
302 for n == -1 || n > 1 {
303 index := strings.IndexAny(s, sep)
304 if index >= 0 {
305 ret = append(ret, s[0:index])
306 s = s[index+1:]
307 if n > 0 {
308 n--
309 }
310 } else {
311 break
312 }
313 }
314 ret = append(ret, s)
315 return ret
316}
Dan Willemsen43398532018-02-21 02:10:29 -0800317
Dan Willemsen43398532018-02-21 02:10:29 -0800318func unescape(s string) string {
319 ret := ""
320 for {
321 index := strings.IndexByte(s, '\\')
322 if index < 0 {
323 break
324 }
325
326 if index+1 == len(s) {
327 break
328 }
329
330 switch s[index+1] {
331 case ' ', '\\', '#', ':', '*', '[', '|', '\t', '\n', '\r':
332 ret += s[:index] + s[index+1:index+2]
333 default:
334 ret += s[:index+2]
335 }
336 s = s[index+2:]
337 }
338 return ret + s
339}