Pre-allocate ninjaString slices

Naively pre-allocate ninjaString slices by counting $ characters as
an estimate of how many variables will be needed.  Saves 5% cpu time
on one workload.

Change-Id: Ib3a41df559d728b2db047f6dbbf9eb06d7045303
diff --git a/ninja_strings.go b/ninja_strings.go
index 3c3b444..d640c87 100644
--- a/ninja_strings.go
+++ b/ninja_strings.go
@@ -80,7 +80,12 @@
 // occurrences are expected to be variables or $$) and returns a list of the
 // variable names that the string references.
 func parseNinjaString(scope scope, str string) (*ninjaString, error) {
-	result := &ninjaString{}
+	// naively pre-allocate slices by counting $ signs
+	n := strings.Count(str, "$")
+	result := &ninjaString{
+		strings:   make([]string, 0, n+1),
+		variables: make([]Variable, 0, n),
+	}
 
 	parseState := &parseState{
 		scope:  scope,
diff --git a/ninja_strings_test.go b/ninja_strings_test.go
index b35c8b4..695f14c 100644
--- a/ninja_strings_test.go
+++ b/ninja_strings_test.go
@@ -66,6 +66,11 @@
 		strs:  []string{"","$$"},
 	},
 	{
+		input: "foo bar",
+		vars:  nil,
+		strs:  []string{"foo bar"},
+	},
+	{
 		input: "foo $ bar",
 		err:   "invalid character after '$' at byte offset 5",
 	},
@@ -90,7 +95,7 @@
 func TestParseNinjaString(t *testing.T) {
 	for _, testCase := range ninjaParseTestCases {
 		scope := newLocalScope(nil, "namespace")
-		var expectedVars []Variable
+		expectedVars := []Variable{}
 		for _, varName := range testCase.vars {
 			v, err := scope.LookupVariable(varName)
 			if err != nil {
@@ -107,7 +112,7 @@
 			if !reflect.DeepEqual(output.variables, expectedVars) {
 				t.Errorf("incorrect variable list:")
 				t.Errorf("     input: %q", testCase.input)
-				t.Errorf("  expected: %#v", testCase.vars)
+				t.Errorf("  expected: %#v", expectedVars)
 				t.Errorf("       got: %#v", output.variables)
 			}
 			if !reflect.DeepEqual(output.strings, testCase.strs) {