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