Browse code

Use a bytes.Buffer for shell_words string concat

It's much faster

Signed-off-by: Daniel Nephin <dnephin@docker.com>

Daniel Nephin authored on 2017/04/14 00:56:37
Showing 1 changed files
... ...
@@ -7,10 +7,12 @@ package dockerfile
7 7
 // be added by adding code to the "special ${} format processing" section
8 8
 
9 9
 import (
10
-	"fmt"
10
+	"bytes"
11 11
 	"strings"
12 12
 	"text/scanner"
13 13
 	"unicode"
14
+
15
+	"github.com/pkg/errors"
14 16
 )
15 17
 
16 18
 type shellWord struct {
... ...
@@ -105,7 +107,7 @@ func (w *wordsStruct) getWords() []string {
105 105
 // Process the word, starting at 'pos', and stop when we get to the
106 106
 // end of the word or the 'stopChar' character
107 107
 func (sw *shellWord) processStopOn(stopChar rune) (string, []string, error) {
108
-	var result string
108
+	var result bytes.Buffer
109 109
 	var words wordsStruct
110 110
 
111 111
 	var charFuncMapping = map[rune]func() (string, error){
... ...
@@ -127,7 +129,7 @@ func (sw *shellWord) processStopOn(stopChar rune) (string, []string, error) {
127 127
 			if err != nil {
128 128
 				return "", []string{}, err
129 129
 			}
130
-			result += tmp
130
+			result.WriteString(tmp)
131 131
 
132 132
 			if ch == rune('$') {
133 133
 				words.addString(tmp)
... ...
@@ -140,7 +142,6 @@ func (sw *shellWord) processStopOn(stopChar rune) (string, []string, error) {
140 140
 
141 141
 			if ch == sw.escapeToken {
142 142
 				// '\' (default escape token, but ` allowed) escapes, except end of line
143
-
144 143
 				ch = sw.scanner.Next()
145 144
 
146 145
 				if ch == scanner.EOF {
... ...
@@ -152,11 +153,11 @@ func (sw *shellWord) processStopOn(stopChar rune) (string, []string, error) {
152 152
 				words.addChar(ch)
153 153
 			}
154 154
 
155
-			result += string(ch)
155
+			result.WriteRune(ch)
156 156
 		}
157 157
 	}
158 158
 
159
-	return result, words.getWords(), nil
159
+	return result.String(), words.getWords(), nil
160 160
 }
161 161
 
162 162
 func (sw *shellWord) processSingleQuote() (string, error) {
... ...
@@ -169,25 +170,20 @@ func (sw *shellWord) processSingleQuote() (string, error) {
169 169
 	//   all the characters (except single quotes, making it impossible to put
170 170
 	//   single-quotes in a single-quoted string).
171 171
 
172
-	var result string
172
+	var result bytes.Buffer
173 173
 
174 174
 	sw.scanner.Next()
175 175
 
176 176
 	for {
177 177
 		ch := sw.scanner.Next()
178
-
179
-		if ch == scanner.EOF {
180
-			return "", fmt.Errorf("unexpected end of statement while looking for matching single-quote")
181
-		}
182
-
183
-		if ch == '\'' {
184
-			break
178
+		switch ch {
179
+		case scanner.EOF:
180
+			return "", errors.New("unexpected end of statement while looking for matching single-quote")
181
+		case '\'':
182
+			return result.String(), nil
185 183
 		}
186
-
187
-		result += string(ch)
184
+		result.WriteRune(ch)
188 185
 	}
189
-
190
-	return result, nil
191 186
 }
192 187
 
193 188
 func (sw *shellWord) processDoubleQuote() (string, error) {
... ...
@@ -203,116 +199,107 @@ func (sw *shellWord) processDoubleQuote() (string, error) {
203 203
 	//    $ ` " \ <newline>.
204 204
 	//  Otherwise it remains literal.
205 205
 
206
-	var result string
206
+	var result bytes.Buffer
207 207
 
208 208
 	sw.scanner.Next()
209 209
 
210 210
 	for {
211
-		ch := sw.scanner.Peek()
212
-
213
-		if ch == scanner.EOF {
214
-			return "", fmt.Errorf("unexpected end of statement while looking for matching double-quote")
215
-		}
216
-
217
-		if ch == '"' {
211
+		switch sw.scanner.Peek() {
212
+		case scanner.EOF:
213
+			return "", errors.New("unexpected end of statement while looking for matching double-quote")
214
+		case '"':
218 215
 			sw.scanner.Next()
219
-			break
220
-		}
221
-
222
-		if ch == '$' {
223
-			tmp, err := sw.processDollar()
216
+			return result.String(), nil
217
+		case '$':
218
+			value, err := sw.processDollar()
224 219
 			if err != nil {
225 220
 				return "", err
226 221
 			}
227
-			result += tmp
228
-		} else {
229
-			ch = sw.scanner.Next()
222
+			result.WriteString(value)
223
+		default:
224
+			ch := sw.scanner.Next()
230 225
 			if ch == sw.escapeToken {
231
-				chNext := sw.scanner.Peek()
232
-
233
-				if chNext == scanner.EOF {
226
+				switch sw.scanner.Peek() {
227
+				case scanner.EOF:
234 228
 					// Ignore \ at end of word
235 229
 					continue
236
-				}
237
-
238
-				// Note: for now don't do anything special with ` chars.
239
-				// Not sure what to do with them anyway since we're not going
240
-				// to execute the text in there (not now anyway).
241
-				if chNext == '"' || chNext == '$' || chNext == sw.escapeToken {
230
+				case '"', '$', sw.escapeToken:
242 231
 					// These chars can be escaped, all other \'s are left as-is
232
+					// Note: for now don't do anything special with ` chars.
233
+					// Not sure what to do with them anyway since we're not going
234
+					// to execute the text in there (not now anyway).
243 235
 					ch = sw.scanner.Next()
244 236
 				}
245 237
 			}
246
-			result += string(ch)
238
+			result.WriteRune(ch)
247 239
 		}
248 240
 	}
249
-
250
-	return result, nil
251 241
 }
252 242
 
253 243
 func (sw *shellWord) processDollar() (string, error) {
254 244
 	sw.scanner.Next()
255
-	ch := sw.scanner.Peek()
256
-	if ch == '{' {
257
-		sw.scanner.Next()
245
+
246
+	// $xxx case
247
+	if sw.scanner.Peek() != '{' {
258 248
 		name := sw.processName()
259
-		ch = sw.scanner.Peek()
260
-		if ch == '}' {
261
-			// Normal ${xx} case
262
-			sw.scanner.Next()
263
-			return sw.getEnv(name), nil
249
+		if name == "" {
250
+			return "$", nil
264 251
 		}
265
-		if ch == ':' {
266
-			// Special ${xx:...} format processing
267
-			// Yes it allows for recursive $'s in the ... spot
252
+		return sw.getEnv(name), nil
253
+	}
268 254
 
269
-			sw.scanner.Next() // skip over :
270
-			modifier := sw.scanner.Next()
255
+	sw.scanner.Next()
256
+	name := sw.processName()
257
+	ch := sw.scanner.Peek()
258
+	if ch == '}' {
259
+		// Normal ${xx} case
260
+		sw.scanner.Next()
261
+		return sw.getEnv(name), nil
262
+	}
263
+	if ch == ':' {
264
+		// Special ${xx:...} format processing
265
+		// Yes it allows for recursive $'s in the ... spot
271 266
 
272
-			word, _, err := sw.processStopOn('}')
273
-			if err != nil {
274
-				return "", err
275
-			}
267
+		sw.scanner.Next() // skip over :
268
+		modifier := sw.scanner.Next()
276 269
 
277
-			// Grab the current value of the variable in question so we
278
-			// can use to to determine what to do based on the modifier
279
-			newValue := sw.getEnv(name)
270
+		word, _, err := sw.processStopOn('}')
271
+		if err != nil {
272
+			return "", err
273
+		}
280 274
 
281
-			switch modifier {
282
-			case '+':
283
-				if newValue != "" {
284
-					newValue = word
285
-				}
286
-				return newValue, nil
275
+		// Grab the current value of the variable in question so we
276
+		// can use to to determine what to do based on the modifier
277
+		newValue := sw.getEnv(name)
287 278
 
288
-			case '-':
289
-				if newValue == "" {
290
-					newValue = word
291
-				}
292
-				return newValue, nil
279
+		switch modifier {
280
+		case '+':
281
+			if newValue != "" {
282
+				newValue = word
283
+			}
284
+			return newValue, nil
293 285
 
294
-			default:
295
-				return "", fmt.Errorf("Unsupported modifier (%c) in substitution: %s", modifier, sw.word)
286
+		case '-':
287
+			if newValue == "" {
288
+				newValue = word
296 289
 			}
290
+			return newValue, nil
291
+
292
+		default:
293
+			return "", errors.Errorf("unsupported modifier (%c) in substitution: %s", modifier, sw.word)
297 294
 		}
298
-		return "", fmt.Errorf("Missing ':' in substitution: %s", sw.word)
299
-	}
300
-	// $xxx case
301
-	name := sw.processName()
302
-	if name == "" {
303
-		return "$", nil
304 295
 	}
305
-	return sw.getEnv(name), nil
296
+	return "", errors.Errorf("missing ':' in substitution: %s", sw.word)
306 297
 }
307 298
 
308 299
 func (sw *shellWord) processName() string {
309 300
 	// Read in a name (alphanumeric or _)
310 301
 	// If it starts with a numeric then just return $#
311
-	var name string
302
+	var name bytes.Buffer
312 303
 
313 304
 	for sw.scanner.Peek() != scanner.EOF {
314 305
 		ch := sw.scanner.Peek()
315
-		if len(name) == 0 && unicode.IsDigit(ch) {
306
+		if name.Len() == 0 && unicode.IsDigit(ch) {
316 307
 			ch = sw.scanner.Next()
317 308
 			return string(ch)
318 309
 		}
... ...
@@ -320,10 +307,10 @@ func (sw *shellWord) processName() string {
320 320
 			break
321 321
 		}
322 322
 		ch = sw.scanner.Next()
323
-		name += string(ch)
323
+		name.WriteRune(ch)
324 324
 	}
325 325
 
326
-	return name
326
+	return name.String()
327 327
 }
328 328
 
329 329
 func (sw *shellWord) getEnv(name string) string {