Refactor blueprint parser nodes to an interface

Refactor the blueprint parser Value object, which contained a Type enum
and members to hold every possible type, into an interface (now called
Expression).  Rename the existing Expression object that represented a binary
operator Operator.

Also adds and fixes some new printer test cases with mulitline expressions.

Change-Id: Icf4a20f92c8c2a27f18df8ca515a9d7f282ff133
diff --git a/unpack_test.go b/unpack_test.go
index 3efd447..7b314dd 100644
--- a/unpack_test.go
+++ b/unpack_test.go
@@ -27,7 +27,7 @@
 
 var validUnpackTestCases = []struct {
 	input  string
-	output interface{}
+	output []interface{}
 	errs   []error
 }{
 	{`
@@ -36,14 +36,16 @@
 			blank: "",
 		}
 		`,
-		struct {
-			Name  *string
-			Blank *string
-			Unset *string
-		}{
-			Name:  proptools.StringPtr("abc"),
-			Blank: proptools.StringPtr(""),
-			Unset: nil,
+		[]interface{}{
+			struct {
+				Name  *string
+				Blank *string
+				Unset *string
+			}{
+				Name:  proptools.StringPtr("abc"),
+				Blank: proptools.StringPtr(""),
+				Unset: nil,
+			},
 		},
 		nil,
 	},
@@ -53,10 +55,12 @@
 			name: "abc",
 		}
 		`,
-		struct {
-			Name string
-		}{
-			Name: "abc",
+		[]interface{}{
+			struct {
+				Name string
+			}{
+				Name: "abc",
+			},
 		},
 		nil,
 	},
@@ -66,10 +70,12 @@
 			isGood: true,
 		}
 		`,
-		struct {
-			IsGood bool
-		}{
-			IsGood: true,
+		[]interface{}{
+			struct {
+				IsGood bool
+			}{
+				IsGood: true,
+			},
 		},
 		nil,
 	},
@@ -80,14 +86,16 @@
 			isBad: false,
 		}
 		`,
-		struct {
-			IsGood *bool
-			IsBad  *bool
-			IsUgly *bool
-		}{
-			IsGood: proptools.BoolPtr(true),
-			IsBad:  proptools.BoolPtr(false),
-			IsUgly: nil,
+		[]interface{}{
+			struct {
+				IsGood *bool
+				IsBad  *bool
+				IsUgly *bool
+			}{
+				IsGood: proptools.BoolPtr(true),
+				IsBad:  proptools.BoolPtr(false),
+				IsUgly: nil,
+			},
 		},
 		nil,
 	},
@@ -99,14 +107,16 @@
 			empty: []
 		}
 		`,
-		struct {
-			Stuff []string
-			Empty []string
-			Nil   []string
-		}{
-			Stuff: []string{"asdf", "jkl;", "qwert", "uiop", "bnm,"},
-			Empty: []string{},
-			Nil:   nil,
+		[]interface{}{
+			struct {
+				Stuff []string
+				Empty []string
+				Nil   []string
+			}{
+				Stuff: []string{"asdf", "jkl;", "qwert", "uiop", "bnm,"},
+				Empty: []string{},
+				Nil:   nil,
+			},
 		},
 		nil,
 	},
@@ -118,13 +128,15 @@
 			}
 		}
 		`,
-		struct {
-			Nested struct {
-				Name string
-			}
-		}{
-			Nested: struct{ Name string }{
-				Name: "abc",
+		[]interface{}{
+			struct {
+				Nested struct {
+					Name string
+				}
+			}{
+				Nested: struct{ Name string }{
+					Name: "abc",
+				},
 			},
 		},
 		nil,
@@ -137,64 +149,14 @@
 			}
 		}
 		`,
-		struct {
-			Nested interface{}
-		}{
-			Nested: &struct{ Name string }{
-				Name: "def",
-			},
-		},
-		nil,
-	},
-
-	{`
-		m {
-			nested: {
-				foo: "abc",
-			},
-			bar: false,
-			baz: ["def", "ghi"],
-		}
-		`,
-		struct {
-			Nested struct {
-				Foo string
-			}
-			Bar bool
-			Baz []string
-		}{
-			Nested: struct{ Foo string }{
-				Foo: "abc",
-			},
-			Bar: false,
-			Baz: []string{"def", "ghi"},
-		},
-		nil,
-	},
-
-	{`
-		m {
-			nested: {
-				foo: "abc",
-			},
-			bar: false,
-			baz: ["def", "ghi"],
-		}
-		`,
-		struct {
-			Nested struct {
-				Foo string `allowNested:"true"`
-			} `blueprint:"filter(allowNested:\"true\")"`
-			Bar bool
-			Baz []string
-		}{
-			Nested: struct {
-				Foo string `allowNested:"true"`
+		[]interface{}{
+			struct {
+				Nested interface{}
 			}{
-				Foo: "abc",
+				Nested: &struct{ Name string }{
+					Name: "def",
+				},
 			},
-			Bar: false,
-			Baz: []string{"def", "ghi"},
 		},
 		nil,
 	},
@@ -208,18 +170,76 @@
 			baz: ["def", "ghi"],
 		}
 		`,
-		struct {
-			Nested struct {
-				Foo string
-			} `blueprint:"filter(allowNested:\"true\")"`
-			Bar bool
-			Baz []string
-		}{
-			Nested: struct{ Foo string }{
-				Foo: "",
+		[]interface{}{
+			struct {
+				Nested struct {
+					Foo string
+				}
+				Bar bool
+				Baz []string
+			}{
+				Nested: struct{ Foo string }{
+					Foo: "abc",
+				},
+				Bar: false,
+				Baz: []string{"def", "ghi"},
 			},
-			Bar: false,
-			Baz: []string{"def", "ghi"},
+		},
+		nil,
+	},
+
+	{`
+		m {
+			nested: {
+				foo: "abc",
+			},
+			bar: false,
+			baz: ["def", "ghi"],
+		}
+		`,
+		[]interface{}{
+			struct {
+				Nested struct {
+					Foo string `allowNested:"true"`
+				} `blueprint:"filter(allowNested:\"true\")"`
+				Bar bool
+				Baz []string
+			}{
+				Nested: struct {
+					Foo string `allowNested:"true"`
+				}{
+					Foo: "abc",
+				},
+				Bar: false,
+				Baz: []string{"def", "ghi"},
+			},
+		},
+		nil,
+	},
+
+	{`
+		m {
+			nested: {
+				foo: "abc",
+			},
+			bar: false,
+			baz: ["def", "ghi"],
+		}
+		`,
+		[]interface{}{
+			struct {
+				Nested struct {
+					Foo string
+				} `blueprint:"filter(allowNested:\"true\")"`
+				Bar bool
+				Baz []string
+			}{
+				Nested: struct{ Foo string }{
+					Foo: "",
+				},
+				Bar: false,
+				Baz: []string{"def", "ghi"},
+			},
 		},
 		[]error{
 			&Error{
@@ -238,20 +258,22 @@
 			},
 		}
 		`,
-		struct {
-			EmbeddedStruct
-			Nested struct {
+		[]interface{}{
+			struct {
 				EmbeddedStruct
-			}
-		}{
-			EmbeddedStruct: EmbeddedStruct{
-				Name: "abc",
-			},
-			Nested: struct {
-				EmbeddedStruct
+				Nested struct {
+					EmbeddedStruct
+				}
 			}{
 				EmbeddedStruct: EmbeddedStruct{
-					Name: "def",
+					Name: "abc",
+				},
+				Nested: struct {
+					EmbeddedStruct
+				}{
+					EmbeddedStruct: EmbeddedStruct{
+						Name: "def",
+					},
 				},
 			},
 		},
@@ -267,20 +289,22 @@
 			},
 		}
 		`,
-		struct {
-			EmbeddedInterface
-			Nested struct {
+		[]interface{}{
+			struct {
 				EmbeddedInterface
-			}
-		}{
-			EmbeddedInterface: &struct{ Name string }{
-				Name: "abc",
-			},
-			Nested: struct {
-				EmbeddedInterface
+				Nested struct {
+					EmbeddedInterface
+				}
 			}{
 				EmbeddedInterface: &struct{ Name string }{
-					Name: "def",
+					Name: "abc",
+				},
+				Nested: struct {
+					EmbeddedInterface
+				}{
+					EmbeddedInterface: &struct{ Name string }{
+						Name: "def",
+					},
 				},
 			},
 		},
@@ -296,25 +320,27 @@
 			},
 		}
 		`,
-		struct {
-			Name string
-			EmbeddedStruct
-			Nested struct {
+		[]interface{}{
+			struct {
 				Name string
 				EmbeddedStruct
-			}
-		}{
-			Name: "abc",
-			EmbeddedStruct: EmbeddedStruct{
-				Name: "abc",
-			},
-			Nested: struct {
-				Name string
-				EmbeddedStruct
+				Nested struct {
+					Name string
+					EmbeddedStruct
+				}
 			}{
-				Name: "def",
+				Name: "abc",
 				EmbeddedStruct: EmbeddedStruct{
+					Name: "abc",
+				},
+				Nested: struct {
+					Name string
+					EmbeddedStruct
+				}{
 					Name: "def",
+					EmbeddedStruct: EmbeddedStruct{
+						Name: "def",
+					},
 				},
 			},
 		},
@@ -330,30 +356,90 @@
 			},
 		}
 		`,
-		struct {
-			Name string
-			EmbeddedInterface
-			Nested struct {
+		[]interface{}{
+			struct {
 				Name string
 				EmbeddedInterface
-			}
-		}{
-			Name: "abc",
-			EmbeddedInterface: &struct{ Name string }{
-				Name: "abc",
-			},
-			Nested: struct {
-				Name string
-				EmbeddedInterface
+				Nested struct {
+					Name string
+					EmbeddedInterface
+				}
 			}{
-				Name: "def",
+				Name: "abc",
 				EmbeddedInterface: &struct{ Name string }{
+					Name: "abc",
+				},
+				Nested: struct {
+					Name string
+					EmbeddedInterface
+				}{
 					Name: "def",
+					EmbeddedInterface: &struct{ Name string }{
+						Name: "def",
+					},
 				},
 			},
 		},
 		nil,
 	},
+
+	// Variables
+	{`
+		list = ["abc"]
+		string = "def"
+		list_with_variable = [string]
+		m {
+			name: string,
+			list: list,
+			list2: list_with_variable,
+		}
+		`,
+		[]interface{}{
+			struct {
+				Name  string
+				List  []string
+				List2 []string
+			}{
+				Name:  "def",
+				List:  []string{"abc"},
+				List2: []string{"def"},
+			},
+		},
+		nil,
+	},
+
+	// Multiple property structs
+	{`
+		m {
+			nested: {
+				name: "abc",
+			}
+		}
+		`,
+		[]interface{}{
+			struct {
+				Nested struct {
+					Name string
+				}
+			}{
+				Nested: struct{ Name string }{
+					Name: "abc",
+				},
+			},
+			struct {
+				Nested struct {
+					Name string
+				}
+			}{
+				Nested: struct{ Name string }{
+					Name: "abc",
+				},
+			},
+			struct {
+			}{},
+		},
+		nil,
+	},
 }
 
 type EmbeddedStruct struct{ Name string }
@@ -362,7 +448,7 @@
 func TestUnpackProperties(t *testing.T) {
 	for _, testCase := range validUnpackTestCases {
 		r := bytes.NewBufferString(testCase.input)
-		file, errs := parser.Parse("", r, nil)
+		file, errs := parser.ParseAndEval("", r, parser.NewScope(nil))
 		if len(errs) != 0 {
 			t.Errorf("test case: %s", testCase.input)
 			t.Errorf("unexpected parse errors:")
@@ -372,30 +458,45 @@
 			t.FailNow()
 		}
 
-		module := file.Defs[0].(*parser.Module)
-		properties := proptools.CloneProperties(reflect.ValueOf(testCase.output))
-		proptools.ZeroProperties(properties.Elem())
-		_, errs = unpackProperties(module.Properties, properties.Interface())
-		if len(errs) != 0 && len(testCase.errs) == 0 {
-			t.Errorf("test case: %s", testCase.input)
-			t.Errorf("unexpected unpack errors:")
-			for _, err := range errs {
-				t.Errorf("  %s", err)
+		for _, def := range file.Defs {
+			module, ok := def.(*parser.Module)
+			if !ok {
+				continue
 			}
-			t.FailNow()
-		} else if !reflect.DeepEqual(errs, testCase.errs) {
-			t.Errorf("test case: %s", testCase.input)
-			t.Errorf("incorrect errors:")
-			t.Errorf("  expected: %+v", testCase.errs)
-			t.Errorf("       got: %+v", errs)
-		}
 
-		output := properties.Elem().Interface()
-		if !reflect.DeepEqual(output, testCase.output) {
-			t.Errorf("test case: %s", testCase.input)
-			t.Errorf("incorrect output:")
-			t.Errorf("  expected: %+v", testCase.output)
-			t.Errorf("       got: %+v", output)
+			output := []interface{}{}
+			for _, p := range testCase.output {
+				output = append(output, proptools.CloneEmptyProperties(reflect.ValueOf(p)).Interface())
+			}
+			_, errs = unpackProperties(module.Properties, output...)
+			if len(errs) != 0 && len(testCase.errs) == 0 {
+				t.Errorf("test case: %s", testCase.input)
+				t.Errorf("unexpected unpack errors:")
+				for _, err := range errs {
+					t.Errorf("  %s", err)
+				}
+				t.FailNow()
+			} else if !reflect.DeepEqual(errs, testCase.errs) {
+				t.Errorf("test case: %s", testCase.input)
+				t.Errorf("incorrect errors:")
+				t.Errorf("  expected: %+v", testCase.errs)
+				t.Errorf("       got: %+v", errs)
+			}
+
+			if len(output) != len(testCase.output) {
+				t.Fatalf("incorrect number of property structs, expected %d got %d",
+					len(testCase.output), len(output))
+			}
+
+			for i := range output {
+				got := reflect.ValueOf(output[i]).Elem().Interface()
+				if !reflect.DeepEqual(got, testCase.output[i]) {
+					t.Errorf("test case: %s", testCase.input)
+					t.Errorf("incorrect output:")
+					t.Errorf("  expected: %+v", testCase.output[i])
+					t.Errorf("       got: %+v", got)
+				}
+			}
 		}
 	}
 }