1// Copyright 2014 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 blueprint
16
17import (
18	"bytes"
19	"fmt"
20	"strings"
21)
22
23const eof = -1
24
25var (
26	defaultEscaper = strings.NewReplacer(
27		"\n", "$\n")
28	inputEscaper = strings.NewReplacer(
29		"\n", "$\n",
30		" ", "$ ")
31	outputEscaper = strings.NewReplacer(
32		"\n", "$\n",
33		" ", "$ ",
34		":", "$:")
35)
36
37type ninjaString struct {
38	strings   []string
39	variables []Variable
40}
41
42type scope interface {
43	LookupVariable(name string) (Variable, error)
44	IsRuleVisible(rule Rule) bool
45	IsPoolVisible(pool Pool) bool
46}
47
48func simpleNinjaString(str string) *ninjaString {
49	return &ninjaString{
50		strings: []string{str},
51	}
52}
53
54type parseState struct {
55	scope       scope
56	str         string
57	stringStart int
58	varStart    int
59	result      *ninjaString
60}
61
62func (ps *parseState) pushVariable(v Variable) {
63	if len(ps.result.variables) == len(ps.result.strings) {
64		// Last push was a variable, we need a blank string separator
65		ps.result.strings = append(ps.result.strings, "")
66	}
67	ps.result.variables = append(ps.result.variables, v)
68}
69
70func (ps *parseState) pushString(s string) {
71	if len(ps.result.strings) != len(ps.result.variables) {
72		panic("oops, pushed string after string")
73	}
74	ps.result.strings = append(ps.result.strings, s)
75}
76
77type stateFunc func(*parseState, int, rune) (stateFunc, error)
78
79// parseNinjaString parses an unescaped ninja string (i.e. all $<something>
80// occurrences are expected to be variables or $$) and returns a list of the
81// variable names that the string references.
82func parseNinjaString(scope scope, str string) (*ninjaString, error) {
83	// naively pre-allocate slices by counting $ signs
84	n := strings.Count(str, "$")
85	result := &ninjaString{
86		strings:   make([]string, 0, n+1),
87		variables: make([]Variable, 0, n),
88	}
89
90	parseState := &parseState{
91		scope:  scope,
92		str:    str,
93		result: result,
94	}
95
96	state := parseStringState
97	var err error
98	for i := 0; i < len(str); i++ {
99		r := rune(str[i])
100		state, err = state(parseState, i, r)
101		if err != nil {
102			return nil, err
103		}
104	}
105
106	_, err = state(parseState, len(parseState.str), eof)
107	if err != nil {
108		return nil, err
109	}
110
111	return result, nil
112}
113
114func parseStringState(state *parseState, i int, r rune) (stateFunc, error) {
115	switch {
116	case r == '$':
117		state.varStart = i + 1
118		return parseDollarStartState, nil
119
120	case r == eof:
121		state.pushString(state.str[state.stringStart:i])
122		return nil, nil
123
124	default:
125		return parseStringState, nil
126	}
127}
128
129func parseDollarStartState(state *parseState, i int, r rune) (stateFunc, error) {
130	switch {
131	case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z',
132		r >= '0' && r <= '9', r == '_', r == '-':
133		// The beginning of a of the variable name.  Output the string and
134		// keep going.
135		state.pushString(state.str[state.stringStart : i-1])
136		return parseDollarState, nil
137
138	case r == '$':
139		// Just a "$$".  Go back to parseStringState without changing
140		// state.stringStart.
141		return parseStringState, nil
142
143	case r == '{':
144		// This is a bracketted variable name (e.g. "${blah.blah}").  Output
145		// the string and keep going.
146		state.pushString(state.str[state.stringStart : i-1])
147		state.varStart = i + 1
148		return parseBracketsState, nil
149
150	case r == eof:
151		return nil, fmt.Errorf("unexpected end of string after '$'")
152
153	default:
154		// This was some arbitrary character following a dollar sign,
155		// which is not allowed.
156		return nil, fmt.Errorf("invalid character after '$' at byte "+
157			"offset %d", i)
158	}
159}
160
161func parseDollarState(state *parseState, i int, r rune) (stateFunc, error) {
162	switch {
163	case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z',
164		r >= '0' && r <= '9', r == '_', r == '-':
165		// A part of the variable name.  Keep going.
166		return parseDollarState, nil
167
168	case r == '$':
169		// A dollar after the variable name (e.g. "$blah$").  Output the
170		// variable we have and start a new one.
171		v, err := state.scope.LookupVariable(state.str[state.varStart:i])
172		if err != nil {
173			return nil, err
174		}
175
176		state.pushVariable(v)
177		state.varStart = i + 1
178		state.stringStart = i
179
180		return parseDollarStartState, nil
181
182	case r == eof:
183		// This is the end of the variable name.
184		v, err := state.scope.LookupVariable(state.str[state.varStart:i])
185		if err != nil {
186			return nil, err
187		}
188
189		state.pushVariable(v)
190
191		// We always end with a string, even if it's an empty one.
192		state.pushString("")
193
194		return nil, nil
195
196	default:
197		// We've just gone past the end of the variable name, so record what
198		// we have.
199		v, err := state.scope.LookupVariable(state.str[state.varStart:i])
200		if err != nil {
201			return nil, err
202		}
203
204		state.pushVariable(v)
205		state.stringStart = i
206		return parseStringState, nil
207	}
208}
209
210func parseBracketsState(state *parseState, i int, r rune) (stateFunc, error) {
211	switch {
212	case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z',
213		r >= '0' && r <= '9', r == '_', r == '-', r == '.':
214		// A part of the variable name.  Keep going.
215		return parseBracketsState, nil
216
217	case r == '}':
218		if state.varStart == i {
219			// The brackets were immediately closed.  That's no good.
220			return nil, fmt.Errorf("empty variable name at byte offset %d",
221				i)
222		}
223
224		// This is the end of the variable name.
225		v, err := state.scope.LookupVariable(state.str[state.varStart:i])
226		if err != nil {
227			return nil, err
228		}
229
230		state.pushVariable(v)
231		state.stringStart = i + 1
232		return parseStringState, nil
233
234	case r == eof:
235		return nil, fmt.Errorf("unexpected end of string in variable name")
236
237	default:
238		// This character isn't allowed in a variable name.
239		return nil, fmt.Errorf("invalid character in variable name at "+
240			"byte offset %d", i)
241	}
242}
243
244func parseNinjaStrings(scope scope, strs []string) ([]*ninjaString,
245	error) {
246
247	if len(strs) == 0 {
248		return nil, nil
249	}
250	result := make([]*ninjaString, len(strs))
251	for i, str := range strs {
252		ninjaStr, err := parseNinjaString(scope, str)
253		if err != nil {
254			return nil, fmt.Errorf("error parsing element %d: %s", i, err)
255		}
256		result[i] = ninjaStr
257	}
258	return result, nil
259}
260
261func (n *ninjaString) Value(pkgNames map[*packageContext]string) string {
262	return n.ValueWithEscaper(pkgNames, defaultEscaper)
263}
264
265func (n *ninjaString) ValueWithEscaper(pkgNames map[*packageContext]string,
266	escaper *strings.Replacer) string {
267
268	str := escaper.Replace(n.strings[0])
269	for i, v := range n.variables {
270		str += "${" + v.fullName(pkgNames) + "}"
271		str += escaper.Replace(n.strings[i+1])
272	}
273	return str
274}
275
276func (n *ninjaString) Eval(variables map[Variable]*ninjaString) (string, error) {
277	str := n.strings[0]
278	for i, v := range n.variables {
279		variable, ok := variables[v]
280		if !ok {
281			return "", fmt.Errorf("no such global variable: %s", v)
282		}
283		value, err := variable.Eval(variables)
284		if err != nil {
285			return "", err
286		}
287		str += value + n.strings[i+1]
288	}
289	return str, nil
290}
291
292func validateNinjaName(name string) error {
293	for i, r := range name {
294		valid := (r >= 'a' && r <= 'z') ||
295			(r >= 'A' && r <= 'Z') ||
296			(r >= '0' && r <= '9') ||
297			(r == '_') ||
298			(r == '-') ||
299			(r == '.')
300		if !valid {
301
302			return fmt.Errorf("%q contains an invalid Ninja name character "+
303				"%q at byte offset %d", name, r, i)
304		}
305	}
306	return nil
307}
308
309func toNinjaName(name string) string {
310	ret := bytes.Buffer{}
311	ret.Grow(len(name))
312	for _, r := range name {
313		valid := (r >= 'a' && r <= 'z') ||
314			(r >= 'A' && r <= 'Z') ||
315			(r >= '0' && r <= '9') ||
316			(r == '_') ||
317			(r == '-') ||
318			(r == '.')
319		if valid {
320			ret.WriteRune(r)
321		} else {
322			ret.WriteRune('_')
323		}
324	}
325
326	return ret.String()
327}
328
329var builtinRuleArgs = []string{"out", "in"}
330
331func validateArgName(argName string) error {
332	err := validateNinjaName(argName)
333	if err != nil {
334		return err
335	}
336
337	// We only allow globals within the rule's package to be used as rule
338	// arguments.  A global in another package can always be mirrored into
339	// the rule's package by defining a new variable, so this doesn't limit
340	// what's possible.  This limitation prevents situations where a Build
341	// invocation in another package must use the rule-defining package's
342	// import name for a 3rd package in order to set the rule's arguments.
343	if strings.ContainsRune(argName, '.') {
344		return fmt.Errorf("%q contains a '.' character", argName)
345	}
346
347	for _, builtin := range builtinRuleArgs {
348		if argName == builtin {
349			return fmt.Errorf("%q conflicts with Ninja built-in", argName)
350		}
351	}
352
353	return nil
354}
355
356func validateArgNames(argNames []string) error {
357	for _, argName := range argNames {
358		err := validateArgName(argName)
359		if err != nil {
360			return err
361		}
362	}
363
364	return nil
365}
366