1 // Copyright 2015 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 15 package kati 16 17 import ( 18 "bytes" 19 "path/filepath" 20 "strings" 21 22 "github.com/golang/glog" 23 ) 24 25 var wsbytes = [256]bool{' ': true, '\t': true, '\n': true, '\r': true} 26 27 // TODO(ukai): use unicode.IsSpace? 28 func isWhitespace(ch rune) bool { 29 if int(ch) >= len(wsbytes) { 30 return false 31 } 32 return wsbytes[ch] 33 } 34 35 func splitSpaces(s string) []string { 36 var r []string 37 tokStart := -1 38 for i, ch := range s { 39 if isWhitespace(ch) { 40 if tokStart >= 0 { 41 r = append(r, s[tokStart:i]) 42 tokStart = -1 43 } 44 } else { 45 if tokStart < 0 { 46 tokStart = i 47 } 48 } 49 } 50 if tokStart >= 0 { 51 r = append(r, s[tokStart:]) 52 } 53 glog.V(2).Infof("splitSpace(%q)=%q", s, r) 54 return r 55 } 56 57 func splitSpacesBytes(s []byte) (r [][]byte) { 58 tokStart := -1 59 for i, ch := range s { 60 if isWhitespace(rune(ch)) { 61 if tokStart >= 0 { 62 r = append(r, s[tokStart:i]) 63 tokStart = -1 64 } 65 } else { 66 if tokStart < 0 { 67 tokStart = i 68 } 69 } 70 } 71 if tokStart >= 0 { 72 r = append(r, s[tokStart:]) 73 } 74 glog.V(2).Infof("splitSpace(%q)=%q", s, r) 75 return r 76 } 77 78 // TODO(ukai): use bufio.Scanner? 79 type wordScanner struct { 80 in []byte 81 s int // word starts 82 i int // current pos 83 esc bool // handle \-escape 84 } 85 86 func newWordScanner(in []byte) *wordScanner { 87 return &wordScanner{ 88 in: in, 89 } 90 } 91 92 func (ws *wordScanner) next() bool { 93 for ws.s = ws.i; ws.s < len(ws.in); ws.s++ { 94 if !wsbytes[ws.in[ws.s]] { 95 break 96 } 97 } 98 if ws.s == len(ws.in) { 99 return false 100 } 101 return true 102 } 103 104 func (ws *wordScanner) Scan() bool { 105 if !ws.next() { 106 return false 107 } 108 for ws.i = ws.s; ws.i < len(ws.in); ws.i++ { 109 if ws.esc && ws.in[ws.i] == '\\' { 110 ws.i++ 111 continue 112 } 113 if wsbytes[ws.in[ws.i]] { 114 break 115 } 116 } 117 return true 118 } 119 120 func (ws *wordScanner) Bytes() []byte { 121 return ws.in[ws.s:ws.i] 122 } 123 124 func (ws *wordScanner) Remain() []byte { 125 if !ws.next() { 126 return nil 127 } 128 return ws.in[ws.s:] 129 } 130 131 func matchPattern(pat, str string) bool { 132 i := strings.IndexByte(pat, '%') 133 if i < 0 { 134 return pat == str 135 } 136 return strings.HasPrefix(str, pat[:i]) && strings.HasSuffix(str, pat[i+1:]) 137 } 138 139 func matchPatternBytes(pat, str []byte) bool { 140 i := bytes.IndexByte(pat, '%') 141 if i < 0 { 142 return bytes.Equal(pat, str) 143 } 144 return bytes.HasPrefix(str, pat[:i]) && bytes.HasSuffix(str, pat[i+1:]) 145 } 146 147 func substPattern(pat, repl, str string) string { 148 ps := strings.SplitN(pat, "%", 2) 149 if len(ps) != 2 { 150 if str == pat { 151 return repl 152 } 153 return str 154 } 155 in := str 156 trimed := str 157 if ps[0] != "" { 158 trimed = strings.TrimPrefix(in, ps[0]) 159 if trimed == in { 160 return str 161 } 162 } 163 in = trimed 164 if ps[1] != "" { 165 trimed = strings.TrimSuffix(in, ps[1]) 166 if trimed == in { 167 return str 168 } 169 } 170 171 rs := strings.SplitN(repl, "%", 2) 172 if len(rs) != 2 { 173 return repl 174 } 175 return rs[0] + trimed + rs[1] 176 } 177 178 func substPatternBytes(pat, repl, str []byte) (pre, subst, post []byte) { 179 i := bytes.IndexByte(pat, '%') 180 if i < 0 { 181 if bytes.Equal(str, pat) { 182 return repl, nil, nil 183 } 184 return str, nil, nil 185 } 186 in := str 187 trimed := str 188 if i > 0 { 189 trimed = bytes.TrimPrefix(in, pat[:i]) 190 if bytes.Equal(trimed, in) { 191 return str, nil, nil 192 } 193 } 194 in = trimed 195 if i < len(pat)-1 { 196 trimed = bytes.TrimSuffix(in, pat[i+1:]) 197 if bytes.Equal(trimed, in) { 198 return str, nil, nil 199 } 200 } 201 202 i = bytes.IndexByte(repl, '%') 203 if i < 0 { 204 return repl, nil, nil 205 } 206 207 return repl[:i], trimed, repl[i+1:] 208 } 209 210 func substRef(pat, repl, str string) string { 211 if strings.IndexByte(pat, '%') >= 0 && strings.IndexByte(repl, '%') >= 0 { 212 return substPattern(pat, repl, str) 213 } 214 str = strings.TrimSuffix(str, pat) 215 return str + repl 216 } 217 218 func stripExt(s string) string { 219 suf := filepath.Ext(s) 220 return s[:len(s)-len(suf)] 221 } 222 223 func trimLeftSpace(s string) string { 224 for i, ch := range s { 225 if !isWhitespace(ch) { 226 return s[i:] 227 } 228 } 229 return "" 230 } 231 232 func trimLeftSpaceBytes(s []byte) []byte { 233 for i, ch := range s { 234 if !isWhitespace(rune(ch)) { 235 return s[i:] 236 } 237 } 238 return nil 239 } 240 241 func trimRightSpaceBytes(s []byte) []byte { 242 for i := len(s) - 1; i >= 0; i-- { 243 ch := s[i] 244 if !isWhitespace(rune(ch)) { 245 return s[:i+1] 246 } 247 } 248 return nil 249 } 250 251 func trimSpaceBytes(s []byte) []byte { 252 s = trimLeftSpaceBytes(s) 253 return trimRightSpaceBytes(s) 254 } 255 256 // Strip leading sequences of './' from file names, so that ./file 257 // and file are considered to be the same file. 258 // From http://www.gnu.org/software/make/manual/make.html#Features 259 func trimLeadingCurdir(s string) string { 260 for strings.HasPrefix(s, "./") { 261 s = s[2:] 262 } 263 return s 264 } 265 266 func contains(list []string, s string) bool { 267 for _, v := range list { 268 if v == s { 269 return true 270 } 271 } 272 return false 273 } 274 275 func firstWord(line []byte) ([]byte, []byte) { 276 s := newWordScanner(line) 277 if s.Scan() { 278 w := s.Bytes() 279 return w, s.Remain() 280 } 281 return line, nil 282 } 283 284 type findCharOption int 285 286 const ( 287 noSkipVar findCharOption = iota 288 skipVar 289 ) 290 291 func findLiteralChar(s []byte, stop1, stop2 byte, op findCharOption) int { 292 i := 0 293 for { 294 var ch byte 295 for i < len(s) { 296 ch = s[i] 297 if ch == '\\' { 298 i += 2 299 continue 300 } 301 if ch == stop1 { 302 break 303 } 304 if ch == stop2 { 305 break 306 } 307 if op == skipVar && ch == '$' { 308 break 309 } 310 i++ 311 } 312 if i >= len(s) { 313 return -1 314 } 315 if ch == '$' { 316 i++ 317 if i == len(s) { 318 return -1 319 } 320 oparen := s[i] 321 cparen := closeParen(oparen) 322 i++ 323 if cparen != 0 { 324 pcount := 1 325 SkipParen: 326 for i < len(s) { 327 ch = s[i] 328 switch ch { 329 case oparen: 330 pcount++ 331 case cparen: 332 pcount-- 333 if pcount == 0 { 334 i++ 335 break SkipParen 336 } 337 } 338 i++ 339 } 340 } 341 continue 342 } 343 return i 344 } 345 } 346 347 func removeComment(line []byte) ([]byte, bool) { 348 var buf []byte 349 for i := 0; i < len(line); i++ { 350 if line[i] != '#' { 351 continue 352 } 353 b := 1 354 for ; i-b >= 0; b++ { 355 if line[i-b] != '\\' { 356 break 357 } 358 } 359 b++ 360 nb := b / 2 361 quoted := b%2 == 1 362 if buf == nil { 363 buf = make([]byte, len(line)) 364 copy(buf, line) 365 line = buf 366 } 367 line = append(line[:i-b+nb+1], line[i:]...) 368 if !quoted { 369 return line[:i-b+nb+1], true 370 } 371 i = i - nb + 1 372 } 373 return line, false 374 } 375 376 // cmdline removes tab at the beginning of lines. 377 func cmdline(line string) string { 378 buf := []byte(line) 379 for i := 0; i < len(buf); i++ { 380 if buf[i] == '\n' && i+1 < len(buf) && buf[i+1] == '\t' { 381 copy(buf[i+1:], buf[i+2:]) 382 buf = buf[:len(buf)-1] 383 } 384 } 385 return string(buf) 386 } 387 388 // concatline removes backslash newline. 389 // TODO: backslash baskslash newline becomes backslash newline. 390 func concatline(line []byte) []byte { 391 var buf []byte 392 for i := 0; i < len(line); i++ { 393 if line[i] != '\\' { 394 continue 395 } 396 if i+1 == len(line) { 397 if line[i-1] != '\\' { 398 line = line[:i] 399 } 400 break 401 } 402 if line[i+1] == '\n' { 403 if buf == nil { 404 buf = make([]byte, len(line)) 405 copy(buf, line) 406 line = buf 407 } 408 oline := trimRightSpaceBytes(line[:i]) 409 oline = append(oline, ' ') 410 nextline := trimLeftSpaceBytes(line[i+2:]) 411 line = append(oline, nextline...) 412 i = len(oline) - 1 413 continue 414 } 415 if i+2 < len(line) && line[i+1] == '\r' && line[i+2] == '\n' { 416 if buf == nil { 417 buf = make([]byte, len(line)) 418 copy(buf, line) 419 line = buf 420 } 421 oline := trimRightSpaceBytes(line[:i]) 422 oline = append(oline, ' ') 423 nextline := trimLeftSpaceBytes(line[i+3:]) 424 line = append(oline, nextline...) 425 i = len(oline) - 1 426 continue 427 } 428 } 429 return line 430 } 431