xref: /aosp_15_r20/build/blueprint/ninja_strings.go (revision 1fa6dee971e1612fa5cc0aa5ca2d35a22e2c34a3)
1*1fa6dee9SAndroid Build Coastguard Worker// Copyright 2014 Google Inc. All rights reserved.
2*1fa6dee9SAndroid Build Coastguard Worker//
3*1fa6dee9SAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*1fa6dee9SAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*1fa6dee9SAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*1fa6dee9SAndroid Build Coastguard Worker//
7*1fa6dee9SAndroid Build Coastguard Worker//     http://www.apache.org/licenses/LICENSE-2.0
8*1fa6dee9SAndroid Build Coastguard Worker//
9*1fa6dee9SAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*1fa6dee9SAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*1fa6dee9SAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*1fa6dee9SAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*1fa6dee9SAndroid Build Coastguard Worker// limitations under the License.
14*1fa6dee9SAndroid Build Coastguard Worker
15*1fa6dee9SAndroid Build Coastguard Workerpackage blueprint
16*1fa6dee9SAndroid Build Coastguard Worker
17*1fa6dee9SAndroid Build Coastguard Workerimport (
18*1fa6dee9SAndroid Build Coastguard Worker	"bytes"
19*1fa6dee9SAndroid Build Coastguard Worker	"fmt"
20*1fa6dee9SAndroid Build Coastguard Worker	"io"
21*1fa6dee9SAndroid Build Coastguard Worker	"slices"
22*1fa6dee9SAndroid Build Coastguard Worker	"strings"
23*1fa6dee9SAndroid Build Coastguard Worker)
24*1fa6dee9SAndroid Build Coastguard Worker
25*1fa6dee9SAndroid Build Coastguard Workerconst eof = -1
26*1fa6dee9SAndroid Build Coastguard Worker
27*1fa6dee9SAndroid Build Coastguard Workervar (
28*1fa6dee9SAndroid Build Coastguard Worker	defaultEscaper = strings.NewReplacer(
29*1fa6dee9SAndroid Build Coastguard Worker		"\n", "$\n")
30*1fa6dee9SAndroid Build Coastguard Worker	inputEscaper = strings.NewReplacer(
31*1fa6dee9SAndroid Build Coastguard Worker		"\n", "$\n",
32*1fa6dee9SAndroid Build Coastguard Worker		" ", "$ ")
33*1fa6dee9SAndroid Build Coastguard Worker	outputEscaper = strings.NewReplacer(
34*1fa6dee9SAndroid Build Coastguard Worker		"\n", "$\n",
35*1fa6dee9SAndroid Build Coastguard Worker		" ", "$ ",
36*1fa6dee9SAndroid Build Coastguard Worker		":", "$:")
37*1fa6dee9SAndroid Build Coastguard Worker)
38*1fa6dee9SAndroid Build Coastguard Worker
39*1fa6dee9SAndroid Build Coastguard Worker// ninjaString contains the parsed result of a string that can contain references to variables (e.g. $cflags) that will
40*1fa6dee9SAndroid Build Coastguard Worker// be propagated to the build.ninja file.  For literal strings with no variable references, the variables field will be
41*1fa6dee9SAndroid Build Coastguard Worker// nil. For strings with variable references str contains the original, unparsed string, and variables contains a
42*1fa6dee9SAndroid Build Coastguard Worker// pointer to a list of references, each with a span of bytes they should replace and a Variable interface.
43*1fa6dee9SAndroid Build Coastguard Workertype ninjaString struct {
44*1fa6dee9SAndroid Build Coastguard Worker	str       string
45*1fa6dee9SAndroid Build Coastguard Worker	variables *[]variableReference
46*1fa6dee9SAndroid Build Coastguard Worker}
47*1fa6dee9SAndroid Build Coastguard Worker
48*1fa6dee9SAndroid Build Coastguard Worker// variableReference contains information about a single reference to a variable (e.g. $cflags) inside a parsed
49*1fa6dee9SAndroid Build Coastguard Worker// ninjaString.  start and end are int32 to reduce memory usage.  A nil variable is a special case of an inserted '$'
50*1fa6dee9SAndroid Build Coastguard Worker// at the beginning of the string to handle leading whitespace that must not be stripped by ninja.
51*1fa6dee9SAndroid Build Coastguard Workertype variableReference struct {
52*1fa6dee9SAndroid Build Coastguard Worker	// start is the offset of the '$' character from the beginning of the unparsed string.
53*1fa6dee9SAndroid Build Coastguard Worker	start int32
54*1fa6dee9SAndroid Build Coastguard Worker
55*1fa6dee9SAndroid Build Coastguard Worker	// end is the offset of the character _after_ the final character of the variable name (or '}' if using the
56*1fa6dee9SAndroid Build Coastguard Worker	//'${}'  syntax)
57*1fa6dee9SAndroid Build Coastguard Worker	end int32
58*1fa6dee9SAndroid Build Coastguard Worker
59*1fa6dee9SAndroid Build Coastguard Worker	variable Variable
60*1fa6dee9SAndroid Build Coastguard Worker}
61*1fa6dee9SAndroid Build Coastguard Worker
62*1fa6dee9SAndroid Build Coastguard Workertype scope interface {
63*1fa6dee9SAndroid Build Coastguard Worker	LookupVariable(name string) (Variable, error)
64*1fa6dee9SAndroid Build Coastguard Worker	IsRuleVisible(rule Rule) bool
65*1fa6dee9SAndroid Build Coastguard Worker	IsPoolVisible(pool Pool) bool
66*1fa6dee9SAndroid Build Coastguard Worker}
67*1fa6dee9SAndroid Build Coastguard Worker
68*1fa6dee9SAndroid Build Coastguard Workerfunc simpleNinjaString(str string) *ninjaString {
69*1fa6dee9SAndroid Build Coastguard Worker	return &ninjaString{str: str}
70*1fa6dee9SAndroid Build Coastguard Worker}
71*1fa6dee9SAndroid Build Coastguard Worker
72*1fa6dee9SAndroid Build Coastguard Workertype parseState struct {
73*1fa6dee9SAndroid Build Coastguard Worker	scope        scope
74*1fa6dee9SAndroid Build Coastguard Worker	str          string
75*1fa6dee9SAndroid Build Coastguard Worker	varStart     int
76*1fa6dee9SAndroid Build Coastguard Worker	varNameStart int
77*1fa6dee9SAndroid Build Coastguard Worker	result       *ninjaString
78*1fa6dee9SAndroid Build Coastguard Worker}
79*1fa6dee9SAndroid Build Coastguard Worker
80*1fa6dee9SAndroid Build Coastguard Workerfunc (ps *parseState) pushVariable(start, end int, v Variable) {
81*1fa6dee9SAndroid Build Coastguard Worker	if ps.result.variables == nil {
82*1fa6dee9SAndroid Build Coastguard Worker		ps.result.variables = &[]variableReference{{start: int32(start), end: int32(end), variable: v}}
83*1fa6dee9SAndroid Build Coastguard Worker	} else {
84*1fa6dee9SAndroid Build Coastguard Worker		*ps.result.variables = append(*ps.result.variables, variableReference{start: int32(start), end: int32(end), variable: v})
85*1fa6dee9SAndroid Build Coastguard Worker	}
86*1fa6dee9SAndroid Build Coastguard Worker}
87*1fa6dee9SAndroid Build Coastguard Worker
88*1fa6dee9SAndroid Build Coastguard Workertype stateFunc func(*parseState, int, rune) (stateFunc, error)
89*1fa6dee9SAndroid Build Coastguard Worker
90*1fa6dee9SAndroid Build Coastguard Worker// parseNinjaString parses an unescaped ninja string (i.e. all $<something>
91*1fa6dee9SAndroid Build Coastguard Worker// occurrences are expected to be variables or $$) and returns a *ninjaString
92*1fa6dee9SAndroid Build Coastguard Worker// that contains the original string and a list of the referenced variables.
93*1fa6dee9SAndroid Build Coastguard Workerfunc parseNinjaString(scope scope, str string) (*ninjaString, error) {
94*1fa6dee9SAndroid Build Coastguard Worker	ninjaString, str, err := parseNinjaOrSimpleString(scope, str)
95*1fa6dee9SAndroid Build Coastguard Worker	if err != nil {
96*1fa6dee9SAndroid Build Coastguard Worker		return nil, err
97*1fa6dee9SAndroid Build Coastguard Worker	}
98*1fa6dee9SAndroid Build Coastguard Worker	if ninjaString != nil {
99*1fa6dee9SAndroid Build Coastguard Worker		return ninjaString, nil
100*1fa6dee9SAndroid Build Coastguard Worker	}
101*1fa6dee9SAndroid Build Coastguard Worker	return simpleNinjaString(str), nil
102*1fa6dee9SAndroid Build Coastguard Worker}
103*1fa6dee9SAndroid Build Coastguard Worker
104*1fa6dee9SAndroid Build Coastguard Worker// parseNinjaOrSimpleString parses an unescaped ninja string (i.e. all $<something>
105*1fa6dee9SAndroid Build Coastguard Worker// occurrences are expected to be variables or $$) and returns either a *ninjaString
106*1fa6dee9SAndroid Build Coastguard Worker// if the string contains ninja variable references, or the original string and nil
107*1fa6dee9SAndroid Build Coastguard Worker// for the *ninjaString if it doesn't.
108*1fa6dee9SAndroid Build Coastguard Workerfunc parseNinjaOrSimpleString(scope scope, str string) (*ninjaString, string, error) {
109*1fa6dee9SAndroid Build Coastguard Worker	// naively pre-allocate slice by counting $ signs
110*1fa6dee9SAndroid Build Coastguard Worker	n := strings.Count(str, "$")
111*1fa6dee9SAndroid Build Coastguard Worker	if n == 0 {
112*1fa6dee9SAndroid Build Coastguard Worker		if len(str) > 0 && str[0] == ' ' {
113*1fa6dee9SAndroid Build Coastguard Worker			str = "$" + str
114*1fa6dee9SAndroid Build Coastguard Worker		}
115*1fa6dee9SAndroid Build Coastguard Worker		return nil, str, nil
116*1fa6dee9SAndroid Build Coastguard Worker	}
117*1fa6dee9SAndroid Build Coastguard Worker	variableReferences := make([]variableReference, 0, n)
118*1fa6dee9SAndroid Build Coastguard Worker	result := &ninjaString{
119*1fa6dee9SAndroid Build Coastguard Worker		str:       str,
120*1fa6dee9SAndroid Build Coastguard Worker		variables: &variableReferences,
121*1fa6dee9SAndroid Build Coastguard Worker	}
122*1fa6dee9SAndroid Build Coastguard Worker
123*1fa6dee9SAndroid Build Coastguard Worker	parseState := &parseState{
124*1fa6dee9SAndroid Build Coastguard Worker		scope:  scope,
125*1fa6dee9SAndroid Build Coastguard Worker		str:    str,
126*1fa6dee9SAndroid Build Coastguard Worker		result: result,
127*1fa6dee9SAndroid Build Coastguard Worker	}
128*1fa6dee9SAndroid Build Coastguard Worker
129*1fa6dee9SAndroid Build Coastguard Worker	state := parseFirstRuneState
130*1fa6dee9SAndroid Build Coastguard Worker	var err error
131*1fa6dee9SAndroid Build Coastguard Worker	for i := 0; i < len(str); i++ {
132*1fa6dee9SAndroid Build Coastguard Worker		r := rune(str[i])
133*1fa6dee9SAndroid Build Coastguard Worker		state, err = state(parseState, i, r)
134*1fa6dee9SAndroid Build Coastguard Worker		if err != nil {
135*1fa6dee9SAndroid Build Coastguard Worker			return nil, "", fmt.Errorf("error parsing ninja string %q: %s", str, err)
136*1fa6dee9SAndroid Build Coastguard Worker		}
137*1fa6dee9SAndroid Build Coastguard Worker	}
138*1fa6dee9SAndroid Build Coastguard Worker
139*1fa6dee9SAndroid Build Coastguard Worker	_, err = state(parseState, len(parseState.str), eof)
140*1fa6dee9SAndroid Build Coastguard Worker	if err != nil {
141*1fa6dee9SAndroid Build Coastguard Worker		return nil, "", err
142*1fa6dee9SAndroid Build Coastguard Worker	}
143*1fa6dee9SAndroid Build Coastguard Worker
144*1fa6dee9SAndroid Build Coastguard Worker	// All the '$' characters counted initially could have been "$$" escapes, leaving no
145*1fa6dee9SAndroid Build Coastguard Worker	// variable references.  Deallocate the variables slice if so.
146*1fa6dee9SAndroid Build Coastguard Worker	if len(*result.variables) == 0 {
147*1fa6dee9SAndroid Build Coastguard Worker		result.variables = nil
148*1fa6dee9SAndroid Build Coastguard Worker	}
149*1fa6dee9SAndroid Build Coastguard Worker
150*1fa6dee9SAndroid Build Coastguard Worker	return result, "", nil
151*1fa6dee9SAndroid Build Coastguard Worker}
152*1fa6dee9SAndroid Build Coastguard Worker
153*1fa6dee9SAndroid Build Coastguard Workerfunc parseFirstRuneState(state *parseState, i int, r rune) (stateFunc, error) {
154*1fa6dee9SAndroid Build Coastguard Worker	if r == ' ' {
155*1fa6dee9SAndroid Build Coastguard Worker		state.pushVariable(0, 1, nil)
156*1fa6dee9SAndroid Build Coastguard Worker	}
157*1fa6dee9SAndroid Build Coastguard Worker	return parseStringState(state, i, r)
158*1fa6dee9SAndroid Build Coastguard Worker}
159*1fa6dee9SAndroid Build Coastguard Worker
160*1fa6dee9SAndroid Build Coastguard Workerfunc parseStringState(state *parseState, i int, r rune) (stateFunc, error) {
161*1fa6dee9SAndroid Build Coastguard Worker	switch {
162*1fa6dee9SAndroid Build Coastguard Worker	case r == '$':
163*1fa6dee9SAndroid Build Coastguard Worker		state.varStart = i
164*1fa6dee9SAndroid Build Coastguard Worker		return parseDollarStartState, nil
165*1fa6dee9SAndroid Build Coastguard Worker
166*1fa6dee9SAndroid Build Coastguard Worker	case r == eof:
167*1fa6dee9SAndroid Build Coastguard Worker		return nil, nil
168*1fa6dee9SAndroid Build Coastguard Worker
169*1fa6dee9SAndroid Build Coastguard Worker	default:
170*1fa6dee9SAndroid Build Coastguard Worker		return parseStringState, nil
171*1fa6dee9SAndroid Build Coastguard Worker	}
172*1fa6dee9SAndroid Build Coastguard Worker}
173*1fa6dee9SAndroid Build Coastguard Worker
174*1fa6dee9SAndroid Build Coastguard Workerfunc parseDollarStartState(state *parseState, i int, r rune) (stateFunc, error) {
175*1fa6dee9SAndroid Build Coastguard Worker	switch {
176*1fa6dee9SAndroid Build Coastguard Worker	case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z',
177*1fa6dee9SAndroid Build Coastguard Worker		r >= '0' && r <= '9', r == '_', r == '-':
178*1fa6dee9SAndroid Build Coastguard Worker		// The beginning of a of the variable name.
179*1fa6dee9SAndroid Build Coastguard Worker		state.varNameStart = i
180*1fa6dee9SAndroid Build Coastguard Worker		return parseDollarState, nil
181*1fa6dee9SAndroid Build Coastguard Worker
182*1fa6dee9SAndroid Build Coastguard Worker	case r == '$':
183*1fa6dee9SAndroid Build Coastguard Worker		// Just a "$$".  Go back to parseStringState.
184*1fa6dee9SAndroid Build Coastguard Worker		return parseStringState, nil
185*1fa6dee9SAndroid Build Coastguard Worker
186*1fa6dee9SAndroid Build Coastguard Worker	case r == '{':
187*1fa6dee9SAndroid Build Coastguard Worker		// This is a bracketted variable name (e.g. "${blah.blah}").
188*1fa6dee9SAndroid Build Coastguard Worker		state.varNameStart = i + 1
189*1fa6dee9SAndroid Build Coastguard Worker		return parseBracketsState, nil
190*1fa6dee9SAndroid Build Coastguard Worker
191*1fa6dee9SAndroid Build Coastguard Worker	case r == eof:
192*1fa6dee9SAndroid Build Coastguard Worker		return nil, fmt.Errorf("unexpected end of string after '$'")
193*1fa6dee9SAndroid Build Coastguard Worker
194*1fa6dee9SAndroid Build Coastguard Worker	default:
195*1fa6dee9SAndroid Build Coastguard Worker		// This was some arbitrary character following a dollar sign,
196*1fa6dee9SAndroid Build Coastguard Worker		// which is not allowed.
197*1fa6dee9SAndroid Build Coastguard Worker		return nil, fmt.Errorf("invalid character after '$' at byte "+
198*1fa6dee9SAndroid Build Coastguard Worker			"offset %d", i)
199*1fa6dee9SAndroid Build Coastguard Worker	}
200*1fa6dee9SAndroid Build Coastguard Worker}
201*1fa6dee9SAndroid Build Coastguard Worker
202*1fa6dee9SAndroid Build Coastguard Workerfunc parseDollarState(state *parseState, i int, r rune) (stateFunc, error) {
203*1fa6dee9SAndroid Build Coastguard Worker	switch {
204*1fa6dee9SAndroid Build Coastguard Worker	case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z',
205*1fa6dee9SAndroid Build Coastguard Worker		r >= '0' && r <= '9', r == '_', r == '-':
206*1fa6dee9SAndroid Build Coastguard Worker		// A part of the variable name.  Keep going.
207*1fa6dee9SAndroid Build Coastguard Worker		return parseDollarState, nil
208*1fa6dee9SAndroid Build Coastguard Worker	}
209*1fa6dee9SAndroid Build Coastguard Worker
210*1fa6dee9SAndroid Build Coastguard Worker	// The variable name has ended, output what we have.
211*1fa6dee9SAndroid Build Coastguard Worker	v, err := state.scope.LookupVariable(state.str[state.varNameStart:i])
212*1fa6dee9SAndroid Build Coastguard Worker	if err != nil {
213*1fa6dee9SAndroid Build Coastguard Worker		return nil, err
214*1fa6dee9SAndroid Build Coastguard Worker	}
215*1fa6dee9SAndroid Build Coastguard Worker
216*1fa6dee9SAndroid Build Coastguard Worker	state.pushVariable(state.varStart, i, v)
217*1fa6dee9SAndroid Build Coastguard Worker
218*1fa6dee9SAndroid Build Coastguard Worker	switch {
219*1fa6dee9SAndroid Build Coastguard Worker	case r == '$':
220*1fa6dee9SAndroid Build Coastguard Worker		// A dollar after the variable name (e.g. "$blah$").  Start a new one.
221*1fa6dee9SAndroid Build Coastguard Worker		state.varStart = i
222*1fa6dee9SAndroid Build Coastguard Worker		return parseDollarStartState, nil
223*1fa6dee9SAndroid Build Coastguard Worker
224*1fa6dee9SAndroid Build Coastguard Worker	case r == eof:
225*1fa6dee9SAndroid Build Coastguard Worker		return nil, nil
226*1fa6dee9SAndroid Build Coastguard Worker
227*1fa6dee9SAndroid Build Coastguard Worker	default:
228*1fa6dee9SAndroid Build Coastguard Worker		return parseStringState, nil
229*1fa6dee9SAndroid Build Coastguard Worker	}
230*1fa6dee9SAndroid Build Coastguard Worker}
231*1fa6dee9SAndroid Build Coastguard Worker
232*1fa6dee9SAndroid Build Coastguard Workerfunc parseBracketsState(state *parseState, i int, r rune) (stateFunc, error) {
233*1fa6dee9SAndroid Build Coastguard Worker	switch {
234*1fa6dee9SAndroid Build Coastguard Worker	case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z',
235*1fa6dee9SAndroid Build Coastguard Worker		r >= '0' && r <= '9', r == '_', r == '-', r == '.':
236*1fa6dee9SAndroid Build Coastguard Worker		// A part of the variable name.  Keep going.
237*1fa6dee9SAndroid Build Coastguard Worker		return parseBracketsState, nil
238*1fa6dee9SAndroid Build Coastguard Worker
239*1fa6dee9SAndroid Build Coastguard Worker	case r == '}':
240*1fa6dee9SAndroid Build Coastguard Worker		if state.varNameStart == i {
241*1fa6dee9SAndroid Build Coastguard Worker			// The brackets were immediately closed.  That's no good.
242*1fa6dee9SAndroid Build Coastguard Worker			return nil, fmt.Errorf("empty variable name at byte offset %d",
243*1fa6dee9SAndroid Build Coastguard Worker				i)
244*1fa6dee9SAndroid Build Coastguard Worker		}
245*1fa6dee9SAndroid Build Coastguard Worker
246*1fa6dee9SAndroid Build Coastguard Worker		// This is the end of the variable name.
247*1fa6dee9SAndroid Build Coastguard Worker		v, err := state.scope.LookupVariable(state.str[state.varNameStart:i])
248*1fa6dee9SAndroid Build Coastguard Worker		if err != nil {
249*1fa6dee9SAndroid Build Coastguard Worker			return nil, err
250*1fa6dee9SAndroid Build Coastguard Worker		}
251*1fa6dee9SAndroid Build Coastguard Worker
252*1fa6dee9SAndroid Build Coastguard Worker		state.pushVariable(state.varStart, i+1, v)
253*1fa6dee9SAndroid Build Coastguard Worker		return parseStringState, nil
254*1fa6dee9SAndroid Build Coastguard Worker
255*1fa6dee9SAndroid Build Coastguard Worker	case r == eof:
256*1fa6dee9SAndroid Build Coastguard Worker		return nil, fmt.Errorf("unexpected end of string in variable name")
257*1fa6dee9SAndroid Build Coastguard Worker
258*1fa6dee9SAndroid Build Coastguard Worker	default:
259*1fa6dee9SAndroid Build Coastguard Worker		// This character isn't allowed in a variable name.
260*1fa6dee9SAndroid Build Coastguard Worker		return nil, fmt.Errorf("invalid character in variable name at "+
261*1fa6dee9SAndroid Build Coastguard Worker			"byte offset %d", i)
262*1fa6dee9SAndroid Build Coastguard Worker	}
263*1fa6dee9SAndroid Build Coastguard Worker}
264*1fa6dee9SAndroid Build Coastguard Worker
265*1fa6dee9SAndroid Build Coastguard Worker// parseNinjaStrings converts a list of strings to *ninjaStrings by finding the references
266*1fa6dee9SAndroid Build Coastguard Worker// to ninja variables contained in the strings.
267*1fa6dee9SAndroid Build Coastguard Workerfunc parseNinjaStrings(scope scope, strs []string) ([]*ninjaString,
268*1fa6dee9SAndroid Build Coastguard Worker	error) {
269*1fa6dee9SAndroid Build Coastguard Worker
270*1fa6dee9SAndroid Build Coastguard Worker	if len(strs) == 0 {
271*1fa6dee9SAndroid Build Coastguard Worker		return nil, nil
272*1fa6dee9SAndroid Build Coastguard Worker	}
273*1fa6dee9SAndroid Build Coastguard Worker	result := make([]*ninjaString, len(strs))
274*1fa6dee9SAndroid Build Coastguard Worker	for i, str := range strs {
275*1fa6dee9SAndroid Build Coastguard Worker		ninjaStr, err := parseNinjaString(scope, str)
276*1fa6dee9SAndroid Build Coastguard Worker		if err != nil {
277*1fa6dee9SAndroid Build Coastguard Worker			return nil, fmt.Errorf("error parsing element %d: %s", i, err)
278*1fa6dee9SAndroid Build Coastguard Worker		}
279*1fa6dee9SAndroid Build Coastguard Worker		result[i] = ninjaStr
280*1fa6dee9SAndroid Build Coastguard Worker	}
281*1fa6dee9SAndroid Build Coastguard Worker	return result, nil
282*1fa6dee9SAndroid Build Coastguard Worker}
283*1fa6dee9SAndroid Build Coastguard Worker
284*1fa6dee9SAndroid Build Coastguard Worker// parseNinjaOrSimpleStrings splits a list of strings into *ninjaStrings if they have ninja
285*1fa6dee9SAndroid Build Coastguard Worker// variable references or a list of strings if they don't.  If none of the input strings contain
286*1fa6dee9SAndroid Build Coastguard Worker// ninja variable references (a very common case) then it returns the unmodified input slice as
287*1fa6dee9SAndroid Build Coastguard Worker// the output slice.
288*1fa6dee9SAndroid Build Coastguard Workerfunc parseNinjaOrSimpleStrings(scope scope, strs []string) ([]*ninjaString, []string, error) {
289*1fa6dee9SAndroid Build Coastguard Worker	if len(strs) == 0 {
290*1fa6dee9SAndroid Build Coastguard Worker		return nil, strs, nil
291*1fa6dee9SAndroid Build Coastguard Worker	}
292*1fa6dee9SAndroid Build Coastguard Worker
293*1fa6dee9SAndroid Build Coastguard Worker	// allSimpleStrings is true until the first time a string with ninja variable references is found.
294*1fa6dee9SAndroid Build Coastguard Worker	allSimpleStrings := true
295*1fa6dee9SAndroid Build Coastguard Worker	var simpleStrings []string
296*1fa6dee9SAndroid Build Coastguard Worker	var ninjaStrings []*ninjaString
297*1fa6dee9SAndroid Build Coastguard Worker
298*1fa6dee9SAndroid Build Coastguard Worker	for i, str := range strs {
299*1fa6dee9SAndroid Build Coastguard Worker		ninjaStr, simpleStr, err := parseNinjaOrSimpleString(scope, str)
300*1fa6dee9SAndroid Build Coastguard Worker		if err != nil {
301*1fa6dee9SAndroid Build Coastguard Worker			return nil, nil, fmt.Errorf("error parsing element %d: %s", i, err)
302*1fa6dee9SAndroid Build Coastguard Worker		} else if ninjaStr != nil {
303*1fa6dee9SAndroid Build Coastguard Worker			ninjaStrings = append(ninjaStrings, ninjaStr)
304*1fa6dee9SAndroid Build Coastguard Worker			if allSimpleStrings && i > 0 {
305*1fa6dee9SAndroid Build Coastguard Worker				// If all previous strings had no ninja variable references then they weren't copied into
306*1fa6dee9SAndroid Build Coastguard Worker				// simpleStrings to avoid allocating it if the input slice is reused as the output.  Allocate
307*1fa6dee9SAndroid Build Coastguard Worker				// simpleStrings and copy all the previous strings into it.
308*1fa6dee9SAndroid Build Coastguard Worker				simpleStrings = make([]string, i, len(strs))
309*1fa6dee9SAndroid Build Coastguard Worker				copy(simpleStrings, strs[:i])
310*1fa6dee9SAndroid Build Coastguard Worker			}
311*1fa6dee9SAndroid Build Coastguard Worker			allSimpleStrings = false
312*1fa6dee9SAndroid Build Coastguard Worker		} else {
313*1fa6dee9SAndroid Build Coastguard Worker			if !allSimpleStrings {
314*1fa6dee9SAndroid Build Coastguard Worker				// Only copy into the output slice if at least one string with ninja variable references
315*1fa6dee9SAndroid Build Coastguard Worker				// was found.  Skipped strings will be copied the first time a string with ninja variable
316*1fa6dee9SAndroid Build Coastguard Worker				// is found.
317*1fa6dee9SAndroid Build Coastguard Worker				simpleStrings = append(simpleStrings, simpleStr)
318*1fa6dee9SAndroid Build Coastguard Worker			}
319*1fa6dee9SAndroid Build Coastguard Worker		}
320*1fa6dee9SAndroid Build Coastguard Worker	}
321*1fa6dee9SAndroid Build Coastguard Worker	if allSimpleStrings {
322*1fa6dee9SAndroid Build Coastguard Worker		// None of the input strings had ninja variable references, return the input slice as the output.
323*1fa6dee9SAndroid Build Coastguard Worker		return nil, strs, nil
324*1fa6dee9SAndroid Build Coastguard Worker	}
325*1fa6dee9SAndroid Build Coastguard Worker	return ninjaStrings, simpleStrings, nil
326*1fa6dee9SAndroid Build Coastguard Worker}
327*1fa6dee9SAndroid Build Coastguard Worker
328*1fa6dee9SAndroid Build Coastguard Workerfunc (n *ninjaString) Value(nameTracker *nameTracker) string {
329*1fa6dee9SAndroid Build Coastguard Worker	if n.variables == nil || len(*n.variables) == 0 {
330*1fa6dee9SAndroid Build Coastguard Worker		return defaultEscaper.Replace(n.str)
331*1fa6dee9SAndroid Build Coastguard Worker	}
332*1fa6dee9SAndroid Build Coastguard Worker	str := &strings.Builder{}
333*1fa6dee9SAndroid Build Coastguard Worker	n.ValueWithEscaper(str, nameTracker, defaultEscaper)
334*1fa6dee9SAndroid Build Coastguard Worker	return str.String()
335*1fa6dee9SAndroid Build Coastguard Worker}
336*1fa6dee9SAndroid Build Coastguard Worker
337*1fa6dee9SAndroid Build Coastguard Workerfunc (n *ninjaString) ValueWithEscaper(w io.StringWriter, nameTracker *nameTracker, escaper *strings.Replacer) {
338*1fa6dee9SAndroid Build Coastguard Worker
339*1fa6dee9SAndroid Build Coastguard Worker	if n.variables == nil || len(*n.variables) == 0 {
340*1fa6dee9SAndroid Build Coastguard Worker		w.WriteString(escaper.Replace(n.str))
341*1fa6dee9SAndroid Build Coastguard Worker		return
342*1fa6dee9SAndroid Build Coastguard Worker	}
343*1fa6dee9SAndroid Build Coastguard Worker
344*1fa6dee9SAndroid Build Coastguard Worker	i := 0
345*1fa6dee9SAndroid Build Coastguard Worker	for _, v := range *n.variables {
346*1fa6dee9SAndroid Build Coastguard Worker		w.WriteString(escaper.Replace(n.str[i:v.start]))
347*1fa6dee9SAndroid Build Coastguard Worker		if v.variable == nil {
348*1fa6dee9SAndroid Build Coastguard Worker			w.WriteString("$ ")
349*1fa6dee9SAndroid Build Coastguard Worker		} else {
350*1fa6dee9SAndroid Build Coastguard Worker			w.WriteString("${")
351*1fa6dee9SAndroid Build Coastguard Worker			w.WriteString(nameTracker.Variable(v.variable))
352*1fa6dee9SAndroid Build Coastguard Worker			w.WriteString("}")
353*1fa6dee9SAndroid Build Coastguard Worker		}
354*1fa6dee9SAndroid Build Coastguard Worker		i = int(v.end)
355*1fa6dee9SAndroid Build Coastguard Worker	}
356*1fa6dee9SAndroid Build Coastguard Worker	w.WriteString(escaper.Replace(n.str[i:len(n.str)]))
357*1fa6dee9SAndroid Build Coastguard Worker}
358*1fa6dee9SAndroid Build Coastguard Worker
359*1fa6dee9SAndroid Build Coastguard Workerfunc (n *ninjaString) Eval(variables map[Variable]*ninjaString) (string, error) {
360*1fa6dee9SAndroid Build Coastguard Worker	if n.variables == nil || len(*n.variables) == 0 {
361*1fa6dee9SAndroid Build Coastguard Worker		return n.str, nil
362*1fa6dee9SAndroid Build Coastguard Worker	}
363*1fa6dee9SAndroid Build Coastguard Worker
364*1fa6dee9SAndroid Build Coastguard Worker	w := &strings.Builder{}
365*1fa6dee9SAndroid Build Coastguard Worker	i := 0
366*1fa6dee9SAndroid Build Coastguard Worker	for _, v := range *n.variables {
367*1fa6dee9SAndroid Build Coastguard Worker		w.WriteString(n.str[i:v.start])
368*1fa6dee9SAndroid Build Coastguard Worker		if v.variable == nil {
369*1fa6dee9SAndroid Build Coastguard Worker			w.WriteString(" ")
370*1fa6dee9SAndroid Build Coastguard Worker		} else {
371*1fa6dee9SAndroid Build Coastguard Worker			variable, ok := variables[v.variable]
372*1fa6dee9SAndroid Build Coastguard Worker			if !ok {
373*1fa6dee9SAndroid Build Coastguard Worker				return "", fmt.Errorf("no such global variable: %s", v.variable)
374*1fa6dee9SAndroid Build Coastguard Worker			}
375*1fa6dee9SAndroid Build Coastguard Worker			value, err := variable.Eval(variables)
376*1fa6dee9SAndroid Build Coastguard Worker			if err != nil {
377*1fa6dee9SAndroid Build Coastguard Worker				return "", err
378*1fa6dee9SAndroid Build Coastguard Worker			}
379*1fa6dee9SAndroid Build Coastguard Worker			w.WriteString(value)
380*1fa6dee9SAndroid Build Coastguard Worker		}
381*1fa6dee9SAndroid Build Coastguard Worker		i = int(v.end)
382*1fa6dee9SAndroid Build Coastguard Worker	}
383*1fa6dee9SAndroid Build Coastguard Worker	w.WriteString(n.str[i:len(n.str)])
384*1fa6dee9SAndroid Build Coastguard Worker	return w.String(), nil
385*1fa6dee9SAndroid Build Coastguard Worker}
386*1fa6dee9SAndroid Build Coastguard Worker
387*1fa6dee9SAndroid Build Coastguard Workerfunc (n *ninjaString) Variables() []Variable {
388*1fa6dee9SAndroid Build Coastguard Worker	if n.variables == nil || len(*n.variables) == 0 {
389*1fa6dee9SAndroid Build Coastguard Worker		return nil
390*1fa6dee9SAndroid Build Coastguard Worker	}
391*1fa6dee9SAndroid Build Coastguard Worker
392*1fa6dee9SAndroid Build Coastguard Worker	variables := make([]Variable, 0, len(*n.variables))
393*1fa6dee9SAndroid Build Coastguard Worker	for _, v := range *n.variables {
394*1fa6dee9SAndroid Build Coastguard Worker		if v.variable != nil {
395*1fa6dee9SAndroid Build Coastguard Worker			variables = append(variables, v.variable)
396*1fa6dee9SAndroid Build Coastguard Worker		}
397*1fa6dee9SAndroid Build Coastguard Worker	}
398*1fa6dee9SAndroid Build Coastguard Worker	return variables
399*1fa6dee9SAndroid Build Coastguard Worker}
400*1fa6dee9SAndroid Build Coastguard Worker
401*1fa6dee9SAndroid Build Coastguard Workerfunc validateNinjaName(name string) error {
402*1fa6dee9SAndroid Build Coastguard Worker	for i, r := range name {
403*1fa6dee9SAndroid Build Coastguard Worker		valid := (r >= 'a' && r <= 'z') ||
404*1fa6dee9SAndroid Build Coastguard Worker			(r >= 'A' && r <= 'Z') ||
405*1fa6dee9SAndroid Build Coastguard Worker			(r >= '0' && r <= '9') ||
406*1fa6dee9SAndroid Build Coastguard Worker			(r == '_') ||
407*1fa6dee9SAndroid Build Coastguard Worker			(r == '-') ||
408*1fa6dee9SAndroid Build Coastguard Worker			(r == '.')
409*1fa6dee9SAndroid Build Coastguard Worker		if !valid {
410*1fa6dee9SAndroid Build Coastguard Worker
411*1fa6dee9SAndroid Build Coastguard Worker			return fmt.Errorf("%q contains an invalid Ninja name character "+
412*1fa6dee9SAndroid Build Coastguard Worker				"%q at byte offset %d", name, r, i)
413*1fa6dee9SAndroid Build Coastguard Worker		}
414*1fa6dee9SAndroid Build Coastguard Worker	}
415*1fa6dee9SAndroid Build Coastguard Worker	return nil
416*1fa6dee9SAndroid Build Coastguard Worker}
417*1fa6dee9SAndroid Build Coastguard Worker
418*1fa6dee9SAndroid Build Coastguard Workerfunc toNinjaName(name string) string {
419*1fa6dee9SAndroid Build Coastguard Worker	ret := bytes.Buffer{}
420*1fa6dee9SAndroid Build Coastguard Worker	ret.Grow(len(name))
421*1fa6dee9SAndroid Build Coastguard Worker	for _, r := range name {
422*1fa6dee9SAndroid Build Coastguard Worker		valid := (r >= 'a' && r <= 'z') ||
423*1fa6dee9SAndroid Build Coastguard Worker			(r >= 'A' && r <= 'Z') ||
424*1fa6dee9SAndroid Build Coastguard Worker			(r >= '0' && r <= '9') ||
425*1fa6dee9SAndroid Build Coastguard Worker			(r == '_') ||
426*1fa6dee9SAndroid Build Coastguard Worker			(r == '-') ||
427*1fa6dee9SAndroid Build Coastguard Worker			(r == '.')
428*1fa6dee9SAndroid Build Coastguard Worker		if valid {
429*1fa6dee9SAndroid Build Coastguard Worker			ret.WriteRune(r)
430*1fa6dee9SAndroid Build Coastguard Worker		} else {
431*1fa6dee9SAndroid Build Coastguard Worker			// TODO(jeffrygaston): do escaping so that toNinjaName won't ever output duplicate
432*1fa6dee9SAndroid Build Coastguard Worker			// names for two different input names
433*1fa6dee9SAndroid Build Coastguard Worker			ret.WriteRune('_')
434*1fa6dee9SAndroid Build Coastguard Worker		}
435*1fa6dee9SAndroid Build Coastguard Worker	}
436*1fa6dee9SAndroid Build Coastguard Worker
437*1fa6dee9SAndroid Build Coastguard Worker	return ret.String()
438*1fa6dee9SAndroid Build Coastguard Worker}
439*1fa6dee9SAndroid Build Coastguard Worker
440*1fa6dee9SAndroid Build Coastguard Workervar builtinRuleArgs = []string{"out", "in"}
441*1fa6dee9SAndroid Build Coastguard Worker
442*1fa6dee9SAndroid Build Coastguard Workerfunc validateArgName(argName string) error {
443*1fa6dee9SAndroid Build Coastguard Worker	err := validateNinjaName(argName)
444*1fa6dee9SAndroid Build Coastguard Worker	if err != nil {
445*1fa6dee9SAndroid Build Coastguard Worker		return err
446*1fa6dee9SAndroid Build Coastguard Worker	}
447*1fa6dee9SAndroid Build Coastguard Worker
448*1fa6dee9SAndroid Build Coastguard Worker	// We only allow globals within the rule's package to be used as rule
449*1fa6dee9SAndroid Build Coastguard Worker	// arguments.  A global in another package can always be mirrored into
450*1fa6dee9SAndroid Build Coastguard Worker	// the rule's package by defining a new variable, so this doesn't limit
451*1fa6dee9SAndroid Build Coastguard Worker	// what's possible.  This limitation prevents situations where a Build
452*1fa6dee9SAndroid Build Coastguard Worker	// invocation in another package must use the rule-defining package's
453*1fa6dee9SAndroid Build Coastguard Worker	// import name for a 3rd package in order to set the rule's arguments.
454*1fa6dee9SAndroid Build Coastguard Worker	if strings.ContainsRune(argName, '.') {
455*1fa6dee9SAndroid Build Coastguard Worker		return fmt.Errorf("%q contains a '.' character", argName)
456*1fa6dee9SAndroid Build Coastguard Worker	}
457*1fa6dee9SAndroid Build Coastguard Worker
458*1fa6dee9SAndroid Build Coastguard Worker	if argName == "tags" {
459*1fa6dee9SAndroid Build Coastguard Worker		return fmt.Errorf("\"tags\" is a reserved argument name")
460*1fa6dee9SAndroid Build Coastguard Worker	}
461*1fa6dee9SAndroid Build Coastguard Worker
462*1fa6dee9SAndroid Build Coastguard Worker	for _, builtin := range builtinRuleArgs {
463*1fa6dee9SAndroid Build Coastguard Worker		if argName == builtin {
464*1fa6dee9SAndroid Build Coastguard Worker			return fmt.Errorf("%q conflicts with Ninja built-in", argName)
465*1fa6dee9SAndroid Build Coastguard Worker		}
466*1fa6dee9SAndroid Build Coastguard Worker	}
467*1fa6dee9SAndroid Build Coastguard Worker
468*1fa6dee9SAndroid Build Coastguard Worker	return nil
469*1fa6dee9SAndroid Build Coastguard Worker}
470*1fa6dee9SAndroid Build Coastguard Worker
471*1fa6dee9SAndroid Build Coastguard Workerfunc validateArgNames(argNames []string) error {
472*1fa6dee9SAndroid Build Coastguard Worker	for _, argName := range argNames {
473*1fa6dee9SAndroid Build Coastguard Worker		err := validateArgName(argName)
474*1fa6dee9SAndroid Build Coastguard Worker		if err != nil {
475*1fa6dee9SAndroid Build Coastguard Worker			return err
476*1fa6dee9SAndroid Build Coastguard Worker		}
477*1fa6dee9SAndroid Build Coastguard Worker	}
478*1fa6dee9SAndroid Build Coastguard Worker
479*1fa6dee9SAndroid Build Coastguard Worker	return nil
480*1fa6dee9SAndroid Build Coastguard Worker}
481*1fa6dee9SAndroid Build Coastguard Worker
482*1fa6dee9SAndroid Build Coastguard Workerfunc ninjaStringsEqual(a, b *ninjaString) bool {
483*1fa6dee9SAndroid Build Coastguard Worker	return a.str == b.str &&
484*1fa6dee9SAndroid Build Coastguard Worker		(a.variables == nil) == (b.variables == nil) &&
485*1fa6dee9SAndroid Build Coastguard Worker		(a.variables == nil ||
486*1fa6dee9SAndroid Build Coastguard Worker			slices.Equal(*a.variables, *b.variables))
487*1fa6dee9SAndroid Build Coastguard Worker}
488