// Copyright 2015 Google Inc. All rights reserved // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kati import ( "bytes" "path/filepath" "strings" "github.com/golang/glog" ) var wsbytes = [256]bool{' ': true, '\t': true, '\n': true, '\r': true} // TODO(ukai): use unicode.IsSpace? func isWhitespace(ch rune) bool { if int(ch) >= len(wsbytes) { return false } return wsbytes[ch] } func splitSpaces(s string) []string { var r []string tokStart := -1 for i, ch := range s { if isWhitespace(ch) { if tokStart >= 0 { r = append(r, s[tokStart:i]) tokStart = -1 } } else { if tokStart < 0 { tokStart = i } } } if tokStart >= 0 { r = append(r, s[tokStart:]) } glog.V(2).Infof("splitSpace(%q)=%q", s, r) return r } func splitSpacesBytes(s []byte) (r [][]byte) { tokStart := -1 for i, ch := range s { if isWhitespace(rune(ch)) { if tokStart >= 0 { r = append(r, s[tokStart:i]) tokStart = -1 } } else { if tokStart < 0 { tokStart = i } } } if tokStart >= 0 { r = append(r, s[tokStart:]) } glog.V(2).Infof("splitSpace(%q)=%q", s, r) return r } // TODO(ukai): use bufio.Scanner? type wordScanner struct { in []byte s int // word starts i int // current pos esc bool // handle \-escape } func newWordScanner(in []byte) *wordScanner { return &wordScanner{ in: in, } } func (ws *wordScanner) next() bool { for ws.s = ws.i; ws.s < len(ws.in); ws.s++ { if !wsbytes[ws.in[ws.s]] { break } } if ws.s == len(ws.in) { return false } return true } func (ws *wordScanner) Scan() bool { if !ws.next() { return false } for ws.i = ws.s; ws.i < len(ws.in); ws.i++ { if ws.esc && ws.in[ws.i] == '\\' { ws.i++ continue } if wsbytes[ws.in[ws.i]] { break } } return true } func (ws *wordScanner) Bytes() []byte { return ws.in[ws.s:ws.i] } func (ws *wordScanner) Remain() []byte { if !ws.next() { return nil } return ws.in[ws.s:] } func matchPattern(pat, str string) bool { i := strings.IndexByte(pat, '%') if i < 0 { return pat == str } return strings.HasPrefix(str, pat[:i]) && strings.HasSuffix(str, pat[i+1:]) } func matchPatternBytes(pat, str []byte) bool { i := bytes.IndexByte(pat, '%') if i < 0 { return bytes.Equal(pat, str) } return bytes.HasPrefix(str, pat[:i]) && bytes.HasSuffix(str, pat[i+1:]) } func substPattern(pat, repl, str string) string { ps := strings.SplitN(pat, "%", 2) if len(ps) != 2 { if str == pat { return repl } return str } in := str trimed := str if ps[0] != "" { trimed = strings.TrimPrefix(in, ps[0]) if trimed == in { return str } } in = trimed if ps[1] != "" { trimed = strings.TrimSuffix(in, ps[1]) if trimed == in { return str } } rs := strings.SplitN(repl, "%", 2) if len(rs) != 2 { return repl } return rs[0] + trimed + rs[1] } func substPatternBytes(pat, repl, str []byte) (pre, subst, post []byte) { i := bytes.IndexByte(pat, '%') if i < 0 { if bytes.Equal(str, pat) { return repl, nil, nil } return str, nil, nil } in := str trimed := str if i > 0 { trimed = bytes.TrimPrefix(in, pat[:i]) if bytes.Equal(trimed, in) { return str, nil, nil } } in = trimed if i < len(pat)-1 { trimed = bytes.TrimSuffix(in, pat[i+1:]) if bytes.Equal(trimed, in) { return str, nil, nil } } i = bytes.IndexByte(repl, '%') if i < 0 { return repl, nil, nil } return repl[:i], trimed, repl[i+1:] } func substRef(pat, repl, str string) string { if strings.IndexByte(pat, '%') >= 0 && strings.IndexByte(repl, '%') >= 0 { return substPattern(pat, repl, str) } str = strings.TrimSuffix(str, pat) return str + repl } func stripExt(s string) string { suf := filepath.Ext(s) return s[:len(s)-len(suf)] } func trimLeftSpace(s string) string { for i, ch := range s { if !isWhitespace(ch) { return s[i:] } } return "" } func trimLeftSpaceBytes(s []byte) []byte { for i, ch := range s { if !isWhitespace(rune(ch)) { return s[i:] } } return nil } func trimRightSpaceBytes(s []byte) []byte { for i := len(s) - 1; i >= 0; i-- { ch := s[i] if !isWhitespace(rune(ch)) { return s[:i+1] } } return nil } func trimSpaceBytes(s []byte) []byte { s = trimLeftSpaceBytes(s) return trimRightSpaceBytes(s) } // Strip leading sequences of './' from file names, so that ./file // and file are considered to be the same file. // From http://www.gnu.org/software/make/manual/make.html#Features func trimLeadingCurdir(s string) string { for strings.HasPrefix(s, "./") { s = s[2:] } return s } func contains(list []string, s string) bool { for _, v := range list { if v == s { return true } } return false } func firstWord(line []byte) ([]byte, []byte) { s := newWordScanner(line) if s.Scan() { w := s.Bytes() return w, s.Remain() } return line, nil } type findCharOption int const ( noSkipVar findCharOption = iota skipVar ) func findLiteralChar(s []byte, stop1, stop2 byte, op findCharOption) int { i := 0 for { var ch byte for i < len(s) { ch = s[i] if ch == '\\' { i += 2 continue } if ch == stop1 { break } if ch == stop2 { break } if op == skipVar && ch == '$' { break } i++ } if i >= len(s) { return -1 } if ch == '$' { i++ if i == len(s) { return -1 } oparen := s[i] cparen := closeParen(oparen) i++ if cparen != 0 { pcount := 1 SkipParen: for i < len(s) { ch = s[i] switch ch { case oparen: pcount++ case cparen: pcount-- if pcount == 0 { i++ break SkipParen } } i++ } } continue } return i } } func removeComment(line []byte) ([]byte, bool) { var buf []byte for i := 0; i < len(line); i++ { if line[i] != '#' { continue } b := 1 for ; i-b >= 0; b++ { if line[i-b] != '\\' { break } } b++ nb := b / 2 quoted := b%2 == 1 if buf == nil { buf = make([]byte, len(line)) copy(buf, line) line = buf } line = append(line[:i-b+nb+1], line[i:]...) if !quoted { return line[:i-b+nb+1], true } i = i - nb + 1 } return line, false } // cmdline removes tab at the beginning of lines. func cmdline(line string) string { buf := []byte(line) for i := 0; i < len(buf); i++ { if buf[i] == '\n' && i+1 < len(buf) && buf[i+1] == '\t' { copy(buf[i+1:], buf[i+2:]) buf = buf[:len(buf)-1] } } return string(buf) } // concatline removes backslash newline. // TODO: backslash baskslash newline becomes backslash newline. func concatline(line []byte) []byte { var buf []byte for i := 0; i < len(line); i++ { if line[i] != '\\' { continue } if i+1 == len(line) { if line[i-1] != '\\' { line = line[:i] } break } if line[i+1] == '\n' { if buf == nil { buf = make([]byte, len(line)) copy(buf, line) line = buf } oline := trimRightSpaceBytes(line[:i]) oline = append(oline, ' ') nextline := trimLeftSpaceBytes(line[i+2:]) line = append(oline, nextline...) i = len(oline) - 1 continue } if i+2 < len(line) && line[i+1] == '\r' && line[i+2] == '\n' { if buf == nil { buf = make([]byte, len(line)) copy(buf, line) line = buf } oline := trimRightSpaceBytes(line[:i]) oline = append(oline, ' ') nextline := trimLeftSpaceBytes(line[i+3:]) line = append(oline, nextline...) i = len(oline) - 1 continue } } return line }