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