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
15package kati
16
17import (
18	"bytes"
19	"path/filepath"
20	"strings"
21
22	"github.com/golang/glog"
23)
24
25var wsbytes = [256]bool{' ': true, '\t': true, '\n': true, '\r': true}
26
27// TODO(ukai): use unicode.IsSpace?
28func isWhitespace(ch rune) bool {
29	if int(ch) >= len(wsbytes) {
30		return false
31	}
32	return wsbytes[ch]
33}
34
35func 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
57func 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?
79type wordScanner struct {
80	in  []byte
81	s   int  // word starts
82	i   int  // current pos
83	esc bool // handle \-escape
84}
85
86func newWordScanner(in []byte) *wordScanner {
87	return &wordScanner{
88		in: in,
89	}
90}
91
92func (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
104func (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
120func (ws *wordScanner) Bytes() []byte {
121	return ws.in[ws.s:ws.i]
122}
123
124func (ws *wordScanner) Remain() []byte {
125	if !ws.next() {
126		return nil
127	}
128	return ws.in[ws.s:]
129}
130
131func 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
139func 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
147func 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
178func 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
210func 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
218func stripExt(s string) string {
219	suf := filepath.Ext(s)
220	return s[:len(s)-len(suf)]
221}
222
223func trimLeftSpace(s string) string {
224	for i, ch := range s {
225		if !isWhitespace(ch) {
226			return s[i:]
227		}
228	}
229	return ""
230}
231
232func 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
241func 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
251func 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
259func trimLeadingCurdir(s string) string {
260	for strings.HasPrefix(s, "./") {
261		s = s[2:]
262	}
263	return s
264}
265
266func 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
275func 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
284type findCharOption int
285
286const (
287	noSkipVar findCharOption = iota
288	skipVar
289)
290
291func 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
347func 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.
377func 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.
390func 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