430 lines
7.8 KiB
Go
430 lines
7.8 KiB
Go
// 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
|
|
}
|