• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2017 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 parser
16
17import (
18	"fmt"
19	"strings"
20	"unicode"
21	"unicode/utf8"
22)
23
24// A MakeString is a string that may contain variable substitutions in it.
25// It can be considered as an alternating list of raw Strings and variable
26// substitutions, where the first and last entries in the list must be raw
27// Strings (possibly empty). The entirety of the text before the first variable,
28// between two variables, and after the last variable will be considered a
29// single String value. A MakeString that starts with a variable will have an
30// empty first raw string, and a MakeString that ends with a  variable will have
31// an empty last raw string.  Two sequential Variables will have an empty raw
32// string between them.
33//
34// The MakeString is stored as two lists, a list of raw Strings and a list
35// of Variables.  The raw string list is always one longer than the variable
36// list.
37//
38// For example, "$(FOO)/bar/baz" will be represented as the
39// following lists:
40//
41//	{
42//	  Strings: ["", "/bar/baz"],
43//	  Variables: ["FOO"]
44//	}
45type MakeString struct {
46	StringPos Pos
47	Strings   []string
48	Variables []Variable
49}
50
51func SimpleMakeString(s string, pos Pos) *MakeString {
52	return &MakeString{
53		StringPos: pos,
54		Strings:   []string{s},
55	}
56}
57
58func (ms *MakeString) Clone() (result *MakeString) {
59	clone := *ms
60	return &clone
61}
62
63func (ms *MakeString) Pos() Pos {
64	return ms.StringPos
65}
66
67func (ms *MakeString) End() Pos {
68	pos := ms.StringPos
69	if len(ms.Strings) > 1 {
70		pos = ms.Variables[len(ms.Variables)-1].End()
71	}
72	return Pos(int(pos) + len(ms.Strings[len(ms.Strings)-1]))
73}
74
75func (ms *MakeString) appendString(s string) {
76	if len(ms.Strings) == 0 {
77		ms.Strings = []string{s}
78		return
79	} else {
80		ms.Strings[len(ms.Strings)-1] += s
81	}
82}
83
84func (ms *MakeString) appendVariable(v Variable) {
85	if len(ms.Strings) == 0 {
86		ms.Strings = []string{"", ""}
87		ms.Variables = []Variable{v}
88	} else {
89		ms.Strings = append(ms.Strings, "")
90		ms.Variables = append(ms.Variables, v)
91	}
92}
93
94func (ms *MakeString) appendMakeString(other *MakeString) {
95	last := len(ms.Strings) - 1
96	ms.Strings[last] += other.Strings[0]
97	ms.Strings = append(ms.Strings, other.Strings[1:]...)
98	ms.Variables = append(ms.Variables, other.Variables...)
99}
100
101func (ms *MakeString) Value(scope Scope) string {
102	if len(ms.Strings) == 0 {
103		return ""
104	} else {
105		ret := unescape(ms.Strings[0])
106		for i := range ms.Strings[1:] {
107			ret += ms.Variables[i].Value(scope)
108			ret += unescape(ms.Strings[i+1])
109		}
110		return ret
111	}
112}
113
114func (ms *MakeString) Dump() string {
115	if len(ms.Strings) == 0 {
116		return ""
117	} else {
118		ret := ms.Strings[0]
119		for i := range ms.Strings[1:] {
120			ret += ms.Variables[i].Dump()
121			ret += ms.Strings[i+1]
122		}
123		return ret
124	}
125}
126
127func (ms *MakeString) Const() bool {
128	return len(ms.Strings) <= 1
129}
130
131func (ms *MakeString) Empty() bool {
132	return len(ms.Strings) == 0 || (len(ms.Strings) == 1 && ms.Strings[0] == "")
133}
134
135func (ms *MakeString) Split(sep string) []*MakeString {
136	return ms.SplitN(sep, -1)
137}
138
139func (ms *MakeString) SplitN(sep string, n int) []*MakeString {
140	return ms.splitNFunc(n, func(s string, n int) []string {
141		return splitAnyN(s, sep, n)
142	})
143}
144
145// Words splits MakeString into multiple makeStrings separated by whitespace.
146// Thus, " a $(X)b  c " will be split into ["a", "$(X)b", "c"].
147// Splitting a MakeString consisting solely of whitespace yields empty array.
148func (ms *MakeString) Words() []*MakeString {
149	var ch rune    // current character
150	const EOF = -1 // no more characters
151	const EOS = -2 // at the end of a string chunk
152
153	// Next character's chunk and position
154	iString := 0
155	iChar := 0
156
157	var words []*MakeString
158	word := SimpleMakeString("", ms.Pos())
159
160	nextChar := func() {
161		if iString >= len(ms.Strings) {
162			ch = EOF
163		} else if iChar >= len(ms.Strings[iString]) {
164			iString++
165			iChar = 0
166			ch = EOS
167		} else {
168			var w int
169			ch, w = utf8.DecodeRuneInString(ms.Strings[iString][iChar:])
170			iChar += w
171		}
172	}
173
174	appendVariableAndAdvance := func() {
175		if iString-1 < len(ms.Variables) {
176			word.appendVariable(ms.Variables[iString-1])
177		}
178		nextChar()
179	}
180
181	appendCharAndAdvance := func(c rune) {
182		if c != EOF {
183			word.appendString(string(c))
184		}
185		nextChar()
186	}
187
188	nextChar()
189	for ch != EOF {
190		// Skip whitespace
191		for ch == ' ' || ch == '\t' {
192			nextChar()
193		}
194		if ch == EOS {
195			// "... $(X)... " case. The current word should be empty.
196			if !word.Empty() {
197				panic(fmt.Errorf("%q: EOS while current word %q is not empty, iString=%d",
198					ms.Dump(), word.Dump(), iString))
199			}
200			appendVariableAndAdvance()
201		}
202		// Copy word
203		for ch != EOF {
204			if ch == ' ' || ch == '\t' {
205				words = append(words, word)
206				word = SimpleMakeString("", ms.Pos())
207				break
208			}
209			if ch == EOS {
210				// "...a$(X)..." case. Append variable to the current word
211				appendVariableAndAdvance()
212			} else {
213				if ch == '\\' {
214					appendCharAndAdvance('\\')
215				}
216				appendCharAndAdvance(ch)
217			}
218		}
219	}
220	if !word.Empty() {
221		words = append(words, word)
222	}
223	return words
224}
225
226func (ms *MakeString) splitNFunc(n int, splitFunc func(s string, n int) []string) []*MakeString {
227	ret := []*MakeString{}
228
229	curMs := SimpleMakeString("", ms.Pos())
230
231	var i int
232	var s string
233	for i, s = range ms.Strings {
234		if n != 0 {
235			split := splitFunc(s, n)
236			if n != -1 {
237				if len(split) > n || len(split) == 0 {
238					panic("oops!")
239				} else {
240					n -= len(split) - 1
241				}
242			}
243			curMs.appendString(split[0])
244
245			for _, r := range split[1:] {
246				ret = append(ret, curMs)
247				curMs = SimpleMakeString(r, ms.Pos())
248			}
249		} else {
250			curMs.appendString(s)
251		}
252
253		if i < len(ms.Strings)-1 {
254			curMs.appendVariable(ms.Variables[i])
255		}
256	}
257
258	ret = append(ret, curMs)
259	return ret
260}
261
262func (ms *MakeString) TrimLeftSpaces() {
263	l := len(ms.Strings[0])
264	ms.Strings[0] = strings.TrimLeftFunc(ms.Strings[0], unicode.IsSpace)
265	ms.StringPos += Pos(len(ms.Strings[0]) - l)
266}
267
268func (ms *MakeString) TrimRightSpaces() {
269	last := len(ms.Strings) - 1
270	ms.Strings[last] = strings.TrimRightFunc(ms.Strings[last], unicode.IsSpace)
271}
272
273func (ms *MakeString) TrimRightOne() {
274	last := len(ms.Strings) - 1
275	if len(ms.Strings[last]) > 1 {
276		ms.Strings[last] = ms.Strings[last][0 : len(ms.Strings[last])-1]
277	}
278}
279
280func (ms *MakeString) EndsWith(ch rune) bool {
281	s := ms.Strings[len(ms.Strings)-1]
282	return len(s) > 0 && s[len(s)-1] == uint8(ch)
283}
284
285func (ms *MakeString) ReplaceLiteral(input string, output string) {
286	for i := range ms.Strings {
287		ms.Strings[i] = strings.Replace(ms.Strings[i], input, output, -1)
288	}
289}
290
291// If MakeString is $(var) after trimming, returns var
292func (ms *MakeString) SingleVariable() (*MakeString, bool) {
293	if len(ms.Strings) != 2 || strings.TrimSpace(ms.Strings[0]) != "" ||
294		strings.TrimSpace(ms.Strings[1]) != "" {
295		return nil, false
296	}
297	return ms.Variables[0].Name, true
298}
299
300func splitAnyN(s, sep string, n int) []string {
301	ret := []string{}
302	for n == -1 || n > 1 {
303		index := strings.IndexAny(s, sep)
304		if index >= 0 {
305			ret = append(ret, s[0:index])
306			s = s[index+1:]
307			if n > 0 {
308				n--
309			}
310		} else {
311			break
312		}
313	}
314	ret = append(ret, s)
315	return ret
316}
317
318func unescape(s string) string {
319	ret := ""
320	for {
321		index := strings.IndexByte(s, '\\')
322		if index < 0 {
323			break
324		}
325
326		if index+1 == len(s) {
327			break
328		}
329
330		switch s[index+1] {
331		case ' ', '\\', '#', ':', '*', '[', '|', '\t', '\n', '\r':
332			ret += s[:index] + s[index+1:index+2]
333		default:
334			ret += s[:index+2]
335		}
336		s = s[index+2:]
337	}
338	return ret + s
339}
340