Home | History | Annotate | Download | only in kati
      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