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