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	"errors"
20	"fmt"
21	"strings"
22)
23
24type pattern struct {
25	prefix, suffix string
26}
27
28func (p pattern) String() string {
29	return p.prefix + "%" + p.suffix
30}
31
32func (p pattern) match(s string) bool {
33	return strings.HasPrefix(s, p.prefix) && strings.HasSuffix(s, p.suffix)
34}
35
36func (p pattern) subst(repl, str string) string {
37	in := str
38	trimed := str
39	if p.prefix != "" {
40		trimed = strings.TrimPrefix(in, p.prefix)
41		if trimed == in {
42			return str
43		}
44	}
45	in = trimed
46	if p.suffix != "" {
47		trimed = strings.TrimSuffix(in, p.suffix)
48		if trimed == in {
49			return str
50		}
51	}
52	rs := strings.SplitN(repl, "%", 2)
53	if len(rs) != 2 {
54		return repl
55	}
56	return rs[0] + trimed + rs[1]
57}
58
59type rule struct {
60	srcpos
61	// outputs is output of the rule.
62	// []string{} for ': xxx'
63	// nil for empty line.
64	outputs []string
65
66	inputs          []string
67	orderOnlyInputs []string
68	outputPatterns  []pattern
69	isDoubleColon   bool
70	isSuffixRule    bool
71	cmds            []string
72	cmdLineno       int
73}
74
75func (r *rule) cmdpos() srcpos {
76	return srcpos{filename: r.filename, lineno: r.cmdLineno}
77}
78
79func isPatternRule(s []byte) (pattern, bool) {
80	i := findLiteralChar(s, '%', 0, noSkipVar)
81	if i < 0 {
82		return pattern{}, false
83	}
84	return pattern{prefix: string(s[:i]), suffix: string(s[i+1:])}, true
85}
86
87func unescapeInput(s []byte) []byte {
88	// only "\ ", "\=" becoms " ", "=" respectively?
89	// other \-escape, such as "\:" keeps "\:".
90	for i := 0; i < len(s); i++ {
91		if s[i] != '\\' {
92			continue
93		}
94		if i+1 < len(s) && s[i+1] == ' ' || s[i+1] == '=' {
95			copy(s[i:], s[i+1:])
96			s = s[:len(s)-1]
97		}
98	}
99	return s
100}
101
102func unescapeTarget(s []byte) []byte {
103	for i := 0; i < len(s); i++ {
104		if s[i] != '\\' {
105			continue
106		}
107		copy(s[i:], s[i+1:])
108		s = s[:len(s)-1]
109	}
110	return s
111}
112
113func (r *rule) parseInputs(s []byte) {
114	ws := newWordScanner(s)
115	ws.esc = true
116	add := func(t string) {
117		r.inputs = append(r.inputs, t)
118	}
119	for ws.Scan() {
120		input := ws.Bytes()
121		if len(input) == 1 && input[0] == '|' {
122			add = func(t string) {
123				r.orderOnlyInputs = append(r.orderOnlyInputs, t)
124			}
125			continue
126		}
127		input = unescapeInput(input)
128		if !hasWildcardMetaByte(input) {
129			add(internBytes(input))
130			continue
131		}
132		m, _ := fsCache.Glob(string(input))
133		if len(m) == 0 {
134			add(internBytes(input))
135			continue
136		}
137		for _, t := range m {
138			add(intern(t))
139		}
140	}
141}
142
143func (r *rule) parseVar(s []byte, rhs expr) (*assignAST, error) {
144	var lhsBytes []byte
145	var op string
146	// TODO(ukai): support override, export.
147	if s[len(s)-1] != '=' {
148		panic(fmt.Sprintf("unexpected lhs %q", s))
149	}
150	switch s[len(s)-2] { // s[len(s)-1] is '='
151	case ':':
152		lhsBytes = trimSpaceBytes(s[:len(s)-2])
153		op = ":="
154	case '+':
155		lhsBytes = trimSpaceBytes(s[:len(s)-2])
156		op = "+="
157	case '?':
158		lhsBytes = trimSpaceBytes(s[:len(s)-2])
159		op = "?="
160	default:
161		lhsBytes = trimSpaceBytes(s[:len(s)-1])
162		op = "="
163	}
164	assign := &assignAST{
165		lhs: literal(string(lhsBytes)),
166		rhs: compactExpr(rhs),
167		op:  op,
168	}
169	assign.srcpos = r.srcpos
170	return assign, nil
171}
172
173// parse parses rule line.
174// line is rule line until '=', or before ';'.
175// line was already expaned, so probably no need to skip var $(xxx) when
176// finding literal char. i.e. $ is parsed as literal '$'.
177// assign is not nil, if line was known as target specific var '<xxx>: <v>=<val>'
178// rhs is not nil, if line ended with '=' (target specific var after evaluated)
179func (r *rule) parse(line []byte, assign *assignAST, rhs expr) (*assignAST, error) {
180	line = trimLeftSpaceBytes(line)
181	// See semicolon.mk.
182	if rhs == nil && (len(line) == 0 || line[0] == ';') {
183		return nil, nil
184	}
185	r.outputs = []string{}
186
187	index := findLiteralChar(line, ':', 0, noSkipVar)
188	if index < 0 {
189		return nil, errors.New("*** missing separator.")
190	}
191
192	first := line[:index]
193	ws := newWordScanner(first)
194	ws.esc = true
195	pat, isFirstPattern := isPatternRule(first)
196	if isFirstPattern {
197		n := 0
198		for ws.Scan() {
199			n++
200			if n > 1 {
201				return nil, errors.New("*** mixed implicit and normal rules: deprecated syntax")
202			}
203		}
204		r.outputPatterns = []pattern{pat}
205	} else {
206		for ws.Scan() {
207			// TODO(ukai): expand raw wildcard for output. any usage?
208			r.outputs = append(r.outputs, internBytes(unescapeTarget(ws.Bytes())))
209		}
210	}
211
212	index++
213	if index < len(line) && line[index] == ':' {
214		r.isDoubleColon = true
215		index++
216	}
217
218	rest := line[index:]
219	if assign != nil {
220		if len(rest) > 0 {
221			panic(fmt.Sprintf("pattern specific var? line:%q", line))
222		}
223		return assign, nil
224	}
225	if rhs != nil {
226		assign, err := r.parseVar(rest, rhs)
227		if err != nil {
228			return nil, err
229		}
230		return assign, nil
231	}
232	index = bytes.IndexByte(rest, ';')
233	if index >= 0 {
234		r.cmds = append(r.cmds, string(rest[index+1:]))
235		rest = rest[:index-1]
236	}
237	index = findLiteralChar(rest, ':', 0, noSkipVar)
238	if index < 0 {
239		r.parseInputs(rest)
240		return nil, nil
241	}
242
243	// %.x: %.y: %.z
244	if isFirstPattern {
245		return nil, errors.New("*** mixed implicit and normal rules: deprecated syntax")
246	}
247
248	second := rest[:index]
249	third := rest[index+1:]
250
251	// r.outputs is already set.
252	ws = newWordScanner(second)
253	if !ws.Scan() {
254		return nil, errors.New("*** missing target pattern.")
255	}
256	outpat, ok := isPatternRule(ws.Bytes())
257	if !ok {
258		return nil, errors.New("*** target pattern contains no '%'.")
259	}
260	r.outputPatterns = []pattern{outpat}
261	if ws.Scan() {
262		return nil, errors.New("*** multiple target patterns.")
263	}
264	r.parseInputs(third)
265
266	return nil, nil
267}
268