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). 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. 33// 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. 37// 38// For example, "$(FOO)/bar/baz" will be represented as the 39// following lists: 40// 41// { 42// Strings: ["", "/bar/baz"], 43// Variables: ["FOO"] 44// } 45type MakeString struct { 46 StringPos Pos 47 Strings []string 48 Variables []Variable 49} 50 51func SimpleMakeString(s string, pos Pos) *MakeString { 52 return &MakeString{ 53 StringPos: pos, 54 Strings: []string{s}, 55 } 56} 57 58func (ms *MakeString) Clone() (result *MakeString) { 59 clone := *ms 60 return &clone 61} 62 63func (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 75func (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 { 105 ret := unescape(ms.Strings[0]) 106 for i := range ms.Strings[1:] { 107 ret += ms.Variables[i].Value(scope) 108 ret += unescape(ms.Strings[i+1]) 109 } 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 { 140 return ms.splitNFunc(n, func(s string, n int) []string { 141 return splitAnyN(s, sep, n) 142 }) 143} 144 145// 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. 148func (ms *MakeString) Words() []*MakeString { 149 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 224} 225 226func (ms *MakeString) splitNFunc(n int, splitFunc func(s string, n int) []string) []*MakeString { 227 ret := []*MakeString{} 228 229 curMs := SimpleMakeString("", ms.Pos()) 230 231 var i int 232 var s string 233 for i, s = range ms.Strings { 234 if n != 0 { 235 split := splitFunc(s, n) 236 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) 247 curMs = SimpleMakeString(r, ms.Pos()) 248 } 249 } else { 250 curMs.appendString(s) 251 } 252 253 if i < len(ms.Strings)-1 { 254 curMs.appendVariable(ms.Variables[i]) 255 } 256 } 257 258 ret = append(ret, curMs) 259 return ret 260} 261 262func (ms *MakeString) TrimLeftSpaces() { 263 l := len(ms.Strings[0]) 264 ms.Strings[0] = strings.TrimLeftFunc(ms.Strings[0], unicode.IsSpace) 265 ms.StringPos += Pos(len(ms.Strings[0]) - l) 266} 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 285func (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 291// 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 300func 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} 317 318func 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} 340