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