Merge changes Id1b9ae00,I48723e00
* changes:
Merge remote-tracking branch 'upstream/master' into upstream_merge
Merge remote-tracking branch 'upstream/master' into upstream_merge
diff --git a/context.go b/context.go
index 1ad1588..e99bbcb 100644
--- a/context.go
+++ b/context.go
@@ -2198,7 +2198,7 @@
newVariationsCh <- mctx.newVariations
}
- if len(mctx.reverseDeps) > 0 || len(mctx.replace) > 0 || len(mctx.rename) > 0 || len(mctx.newModules) > 0 {
+ if len(mctx.reverseDeps) > 0 || len(mctx.replace) > 0 || len(mctx.rename) > 0 || len(mctx.newModules) > 0 || len(mctx.ninjaFileDeps) > 0 {
globalStateCh <- globalStateChange{
reverse: mctx.reverseDeps,
replace: mctx.replace,
diff --git a/parser/ast.go b/parser/ast.go
index 57c4948..fb7e516 100644
--- a/parser/ast.go
+++ b/parser/ast.go
@@ -130,7 +130,7 @@
return hackyExpressionsAreSame(a, b)
}
-// TODO(jeffrygaston) once positions are removed from Expression stucts,
+// TODO(jeffrygaston) once positions are removed from Expression structs,
// remove this function and have callers use reflect.DeepEqual(a, b)
func hackyExpressionsAreSame(a Expression, b Expression) (equal bool, err error) {
if a.Type() != b.Type() {
diff --git a/parser/parser.go b/parser/parser.go
index 6ae5df3..9b6aa18 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -549,10 +549,6 @@
var elements []Expression
for p.tok != ']' {
element := p.parseExpression()
- if p.eval && element.Type() != StringType {
- p.errorf("Expected string in list, found %s", element.Type().String())
- return nil
- }
elements = append(elements, element)
if p.tok != ',' {
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 70151ad..c9d284b 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -193,6 +193,158 @@
nil,
},
+ {
+ `
+ foo {
+ list_of_maps: [
+ {
+ var: true,
+ name: "a",
+ },
+ {
+ var: false,
+ name: "b",
+ },
+ ],
+ }
+`,
+ []Definition{
+ &Module{
+ Type: "foo",
+ TypePos: mkpos(3, 2, 3),
+ Map: Map{
+ LBracePos: mkpos(7, 2, 7),
+ RBracePos: mkpos(127, 13, 3),
+ Properties: []*Property{
+ {
+ Name: "list_of_maps",
+ NamePos: mkpos(12, 3, 4),
+ ColonPos: mkpos(24, 3, 16),
+ Value: &List{
+ LBracePos: mkpos(26, 3, 18),
+ RBracePos: mkpos(122, 12, 4),
+ Values: []Expression{
+ &Map{
+ LBracePos: mkpos(32, 4, 5),
+ RBracePos: mkpos(70, 7, 5),
+ Properties: []*Property{
+ {
+ Name: "var",
+ NamePos: mkpos(39, 5, 6),
+ ColonPos: mkpos(42, 5, 9),
+ Value: &Bool{
+ LiteralPos: mkpos(44, 5, 11),
+ Value: true,
+ Token: "true",
+ },
+ },
+ {
+ Name: "name",
+ NamePos: mkpos(55, 6, 6),
+ ColonPos: mkpos(59, 6, 10),
+ Value: &String{
+ LiteralPos: mkpos(61, 6, 12),
+ Value: "a",
+ },
+ },
+ },
+ },
+ &Map{
+ LBracePos: mkpos(77, 8, 5),
+ RBracePos: mkpos(116, 11, 5),
+ Properties: []*Property{
+ {
+ Name: "var",
+ NamePos: mkpos(84, 9, 6),
+ ColonPos: mkpos(87, 9, 9),
+ Value: &Bool{
+ LiteralPos: mkpos(89, 9, 11),
+ Value: false,
+ Token: "false",
+ },
+ },
+ {
+ Name: "name",
+ NamePos: mkpos(101, 10, 6),
+ ColonPos: mkpos(105, 10, 10),
+ Value: &String{
+ LiteralPos: mkpos(107, 10, 12),
+ Value: "b",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ nil,
+ },
+ {
+ `
+ foo {
+ list_of_lists: [
+ [ "a", "b" ],
+ [ "c", "d" ]
+ ],
+ }
+`,
+ []Definition{
+ &Module{
+ Type: "foo",
+ TypePos: mkpos(3, 2, 3),
+ Map: Map{
+ LBracePos: mkpos(7, 2, 7),
+ RBracePos: mkpos(72, 7, 3),
+ Properties: []*Property{
+ {
+ Name: "list_of_lists",
+ NamePos: mkpos(12, 3, 4),
+ ColonPos: mkpos(25, 3, 17),
+ Value: &List{
+ LBracePos: mkpos(27, 3, 19),
+ RBracePos: mkpos(67, 6, 4),
+ Values: []Expression{
+ &List{
+ LBracePos: mkpos(33, 4, 5),
+ RBracePos: mkpos(44, 4, 16),
+ Values: []Expression{
+ &String{
+ LiteralPos: mkpos(35, 4, 7),
+ Value: "a",
+ },
+ &String{
+ LiteralPos: mkpos(40, 4, 12),
+ Value: "b",
+ },
+ },
+ },
+ &List{
+ LBracePos: mkpos(51, 5, 5),
+ RBracePos: mkpos(62, 5, 16),
+ Values: []Expression{
+ &String{
+ LiteralPos: mkpos(53, 5, 7),
+ Value: "c",
+ },
+ &String{
+ LiteralPos: mkpos(58, 5, 12),
+ Value: "d",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ nil,
+ },
{`
foo {
stuff: {
@@ -1032,7 +1184,7 @@
for i := range file.Defs {
if !reflect.DeepEqual(file.Defs[i], testCase.defs[i]) {
t.Errorf("test case: %s", testCase.input)
- t.Errorf("incorrect defintion %d:", i)
+ t.Errorf("incorrect definition %d:", i)
t.Errorf(" expected: %s", testCase.defs[i])
t.Errorf(" got: %s", file.Defs[i])
}
diff --git a/parser/printer_test.go b/parser/printer_test.go
index 6f76b26..077a782 100644
--- a/parser/printer_test.go
+++ b/parser/printer_test.go
@@ -361,6 +361,73 @@
}
`,
},
+ {
+ input: `
+// test
+stuff {
+ namespace: "google",
+ string_vars: [
+ {
+ var: "one",
+ values: [ "one_a", "one_b",],
+ },
+ {
+ var: "two",
+ values: [ "two_a", "two_b", ],
+ },
+ ],
+}`,
+ output: `
+// test
+stuff {
+ namespace: "google",
+ string_vars: [
+ {
+ var: "one",
+ values: [
+ "one_a",
+ "one_b",
+ ],
+ },
+ {
+ var: "two",
+ values: [
+ "two_a",
+ "two_b",
+ ],
+ },
+ ],
+}
+`,
+ },
+ {
+ input: `
+// test
+stuff {
+ namespace: "google",
+ list_of_lists: [
+ [ "a", "b" ],
+ [ "c", "d" ],
+ ],
+}
+`,
+ output: `
+// test
+stuff {
+ namespace: "google",
+ list_of_lists: [
+ [
+ "a",
+ "b",
+ ],
+ [
+ "c",
+ "d",
+ ],
+ ],
+}
+`,
+ },
}
func TestPrinter(t *testing.T) {
diff --git a/parser/sort.go b/parser/sort.go
index c8bf74f..da594db 100644
--- a/parser/sort.go
+++ b/parser/sort.go
@@ -33,6 +33,9 @@
}
func SortList(file *File, list *List) {
+ if !isListOfPrimitives(list.Values) {
+ return
+ }
for i := 0; i < len(list.Values); i++ {
// Find a set of values on contiguous lines
line := list.Values[i].Pos().Line
@@ -91,6 +94,9 @@
}
func sortSubList(values []Expression, nextPos scanner.Position, file *File) {
+ if !isListOfPrimitives(values) {
+ return
+ }
l := make(elemList, len(values))
for i, v := range values {
s, ok := v.(*String)
@@ -135,6 +141,9 @@
}
func subListIsSorted(values []Expression) bool {
+ if !isListOfPrimitives(values) {
+ return true
+ }
prev := ""
for _, v := range values {
s, ok := v.(*String)
@@ -184,3 +193,15 @@
func (l commentsByOffset) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}
+
+func isListOfPrimitives(values []Expression) bool {
+ if len(values) == 0 {
+ return true
+ }
+ switch values[0].Type() {
+ case BoolType, StringType, Int64Type:
+ return true
+ default:
+ return false
+ }
+}
diff --git a/proptools/extend_test.go b/proptools/extend_test.go
index d591ce6..0470379 100644
--- a/proptools/extend_test.go
+++ b/proptools/extend_test.go
@@ -412,6 +412,63 @@
order: Replace,
},
{
+ // Append slice of structs
+ in1: &struct{ S []struct{ F string } }{
+ S: []struct{ F string }{
+ {F: "foo"}, {F: "bar"},
+ },
+ },
+ in2: &struct{ S []struct{ F string } }{
+ S: []struct{ F string }{
+ {F: "baz"},
+ },
+ },
+ out: &struct{ S []struct{ F string } }{
+ S: []struct{ F string }{
+ {F: "foo"}, {F: "bar"}, {F: "baz"},
+ },
+ },
+ order: Append,
+ },
+ {
+ // Prepend slice of structs
+ in1: &struct{ S []struct{ F string } }{
+ S: []struct{ F string }{
+ {F: "foo"}, {F: "bar"},
+ },
+ },
+ in2: &struct{ S []struct{ F string } }{
+ S: []struct{ F string }{
+ {F: "baz"},
+ },
+ },
+ out: &struct{ S []struct{ F string } }{
+ S: []struct{ F string }{
+ {F: "baz"}, {F: "foo"}, {F: "bar"},
+ },
+ },
+ order: Prepend,
+ },
+ {
+ // Replace slice of structs
+ in1: &struct{ S []struct{ F string } }{
+ S: []struct{ F string }{
+ {F: "foo"}, {F: "bar"},
+ },
+ },
+ in2: &struct{ S []struct{ F string } }{
+ S: []struct{ F string }{
+ {F: "baz"},
+ },
+ },
+ out: &struct{ S []struct{ F string } }{
+ S: []struct{ F string }{
+ {F: "baz"},
+ },
+ },
+ order: Replace,
+ },
+ {
// Append pointer
in1: &struct{ S *struct{ S string } }{
S: &struct{ S string }{
diff --git a/proptools/proptools.go b/proptools/proptools.go
index c44a4a8..2aa6e32 100644
--- a/proptools/proptools.go
+++ b/proptools/proptools.go
@@ -121,3 +121,7 @@
func isStructPtr(t reflect.Type) bool {
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
}
+
+func isSlice(t reflect.Type) bool {
+ return t.Kind() == reflect.Slice
+}
diff --git a/proptools/unpack.go b/proptools/unpack.go
index 344327f..4a0858c 100644
--- a/proptools/unpack.go
+++ b/proptools/unpack.go
@@ -17,6 +17,9 @@
import (
"fmt"
"reflect"
+ "sort"
+ "strconv"
+ "strings"
"text/scanner"
"github.com/google/blueprint/parser"
@@ -33,103 +36,153 @@
return fmt.Sprintf("%s: %s", e.Pos, e.Err)
}
+// packedProperty helps to track properties usage (`used` will be true)
type packedProperty struct {
property *parser.Property
- unpacked bool
+ used bool
}
-func UnpackProperties(propertyDefs []*parser.Property,
- propertiesStructs ...interface{}) (map[string]*parser.Property, []error) {
+// unpackContext keeps compound names and their values in a map. It is initialized from
+// parsed properties.
+type unpackContext struct {
+ propertyMap map[string]*packedProperty
+ errs []error
+}
- propertyMap := make(map[string]*packedProperty)
- errs := buildPropertyMap("", propertyDefs, propertyMap)
- if len(errs) > 0 {
- return nil, errs
+// UnpackProperties populates the list of runtime values ("property structs") from the parsed properties.
+// If a property a.b.c has a value, a field with the matching name in each runtime value is initialized
+// from it. See PropertyNameForField for field and property name matching.
+// For instance, if the input contains
+// { foo: "abc", bar: {x: 1},}
+// and a runtime value being has been declared as
+// var v struct { Foo string; Bar int }
+// then v.Foo will be set to "abc" and v.Bar will be set to 1
+// (cf. unpack_test.go for further examples)
+//
+// The type of a receiving field has to match the property type, i.e., a bool/int/string field
+// can be set from a property with bool/int/string value, a struct can be set from a map (only the
+// matching fields are set), and an slice can be set from a list.
+// If a field of a runtime value has been already set prior to the UnpackProperties, the new value
+// is appended to it (see somewhat inappropriately named ExtendBasicType).
+// The same property can initialize fields in multiple runtime values. It is an error if any property
+// value was not used to initialize at least one field.
+func UnpackProperties(properties []*parser.Property, objects ...interface{}) (map[string]*parser.Property, []error) {
+ var unpackContext unpackContext
+ unpackContext.propertyMap = make(map[string]*packedProperty)
+ if !unpackContext.buildPropertyMap("", properties) {
+ return nil, unpackContext.errs
}
- for _, properties := range propertiesStructs {
- propertiesValue := reflect.ValueOf(properties)
- if !isStructPtr(propertiesValue.Type()) {
+ for _, obj := range objects {
+ valueObject := reflect.ValueOf(obj)
+ if !isStructPtr(valueObject.Type()) {
panic(fmt.Errorf("properties must be *struct, got %s",
- propertiesValue.Type()))
+ valueObject.Type()))
}
- propertiesValue = propertiesValue.Elem()
-
- newErrs := unpackStructValue("", propertiesValue, propertyMap)
- errs = append(errs, newErrs...)
-
- if len(errs) >= maxUnpackErrors {
- return nil, errs
+ unpackContext.unpackToStruct("", valueObject.Elem())
+ if len(unpackContext.errs) >= maxUnpackErrors {
+ return nil, unpackContext.errs
}
}
- // Report any properties that didn't have corresponding struct fields as
- // errors.
+ // Gather property map, and collect any unused properties.
+ // Avoid reporting subproperties of unused properties.
result := make(map[string]*parser.Property)
- for name, packedProperty := range propertyMap {
- result[name] = packedProperty.property
- if !packedProperty.unpacked {
- err := &UnpackError{
- Err: fmt.Errorf("unrecognized property %q", name),
- Pos: packedProperty.property.ColonPos,
- }
- errs = append(errs, err)
+ var unusedNames []string
+ for name, v := range unpackContext.propertyMap {
+ if v.used {
+ result[name] = v.property
+ } else {
+ unusedNames = append(unusedNames, name)
}
}
-
- if len(errs) > 0 {
- return nil, errs
+ if len(unusedNames) == 0 && len(unpackContext.errs) == 0 {
+ return result, nil
}
-
- return result, nil
+ return nil, unpackContext.reportUnusedNames(unusedNames)
}
-func buildPropertyMap(namePrefix string, propertyDefs []*parser.Property,
- propertyMap map[string]*packedProperty) (errs []error) {
-
- for _, propertyDef := range propertyDefs {
- name := namePrefix + propertyDef.Name
- if first, present := propertyMap[name]; present {
- if first.property == propertyDef {
- // We've already added this property.
+func (ctx *unpackContext) reportUnusedNames(unusedNames []string) []error {
+ sort.Strings(unusedNames)
+ var lastReported string
+ for _, name := range unusedNames {
+ // if 'foo' has been reported, ignore 'foo\..*' and 'foo\[.*'
+ if lastReported != "" {
+ trimmed := strings.TrimPrefix(name, lastReported)
+ if trimmed != name && (trimmed[0] == '.' || trimmed[0] == '[') {
continue
}
- errs = append(errs, &UnpackError{
- Err: fmt.Errorf("property %q already defined", name),
- Pos: propertyDef.ColonPos,
- })
- errs = append(errs, &UnpackError{
- Err: fmt.Errorf("<-- previous definition here"),
- Pos: first.property.ColonPos,
- })
- if len(errs) >= maxUnpackErrors {
- return errs
+ }
+ ctx.errs = append(ctx.errs, &UnpackError{
+ fmt.Errorf("unrecognized property %q", name),
+ ctx.propertyMap[name].property.ColonPos})
+ lastReported = name
+ }
+ return ctx.errs
+}
+
+func (ctx *unpackContext) buildPropertyMap(prefix string, properties []*parser.Property) bool {
+ nOldErrors := len(ctx.errs)
+ for _, property := range properties {
+ name := fieldPath(prefix, property.Name)
+ if first, present := ctx.propertyMap[name]; present {
+ ctx.addError(
+ &UnpackError{fmt.Errorf("property %q already defined", name), property.ColonPos})
+ if ctx.addError(
+ &UnpackError{fmt.Errorf("<-- previous definition here"), first.property.ColonPos}) {
+ return false
}
continue
}
- propertyMap[name] = &packedProperty{
- property: propertyDef,
- unpacked: false,
- }
+ ctx.propertyMap[name] = &packedProperty{property, false}
+ switch propValue := property.Value.Eval().(type) {
+ case *parser.Map:
+ ctx.buildPropertyMap(name, propValue.Properties)
+ case *parser.List:
+ // If it is a list, unroll it unless its elements are of primitive type
+ // (no further mapping will be needed in that case, so we avoid cluttering
+ // the map).
+ if len(propValue.Values) == 0 {
+ continue
+ }
+ if t := propValue.Values[0].Type(); t == parser.StringType || t == parser.Int64Type || t == parser.BoolType {
+ continue
+ }
- // We intentionally do not rescursively add MapValue properties to the
- // property map here. Instead we add them when we encounter a struct
- // into which they can be unpacked. We do this so that if we never
- // encounter such a struct then the "unrecognized property" error will
- // be reported only once for the map property and not for each of its
- // sub-properties.
+ itemProperties := make([]*parser.Property, len(propValue.Values), len(propValue.Values))
+ for i, expr := range propValue.Values {
+ itemProperties[i] = &parser.Property{
+ Name: property.Name + "[" + strconv.Itoa(i) + "]",
+ NamePos: property.NamePos,
+ ColonPos: property.ColonPos,
+ Value: expr,
+ }
+ }
+ if !ctx.buildPropertyMap(prefix, itemProperties) {
+ return false
+ }
+ }
}
- return
+ return len(ctx.errs) == nOldErrors
}
-func unpackStructValue(namePrefix string, structValue reflect.Value,
- propertyMap map[string]*packedProperty) []error {
+func fieldPath(prefix, fieldName string) string {
+ if prefix == "" {
+ return fieldName
+ }
+ return prefix + "." + fieldName
+}
+func (ctx *unpackContext) addError(e error) bool {
+ ctx.errs = append(ctx.errs, e)
+ return len(ctx.errs) < maxUnpackErrors
+}
+
+func (ctx *unpackContext) unpackToStruct(namePrefix string, structValue reflect.Value) {
structType := structValue.Type()
- var errs []error
for i := 0; i < structValue.NumField(); i++ {
fieldValue := structValue.Field(i)
field := structType.Field(i)
@@ -148,14 +201,14 @@
continue
}
- propertyName := namePrefix + PropertyNameForField(field.Name)
+ propertyName := fieldPath(namePrefix, PropertyNameForField(field.Name))
if !fieldValue.CanSet() {
panic(fmt.Errorf("field %s is not settable", propertyName))
}
// Get the property value if it was specified.
- packedProperty, propertyIsSet := propertyMap[propertyName]
+ packedProperty, propertyIsSet := ctx.propertyMap[propertyName]
origFieldValue := fieldValue
@@ -164,15 +217,8 @@
// TODO(ccross): we don't validate types inside nil struct pointers
// Move type validation to a function that runs on each factory once
switch kind := fieldValue.Kind(); kind {
- case reflect.Bool, reflect.String, reflect.Struct:
+ case reflect.Bool, reflect.String, reflect.Struct, reflect.Slice:
// Do nothing
- case reflect.Slice:
- elemType := field.Type.Elem()
- if elemType.Kind() != reflect.String {
- if !HasTag(field, "blueprint", "mutated") {
- panic(fmt.Errorf("field %s is a non-string slice", propertyName))
- }
- }
case reflect.Interface:
if fieldValue.IsNil() {
panic(fmt.Errorf("field %s contains a nil interface", propertyName))
@@ -210,8 +256,7 @@
}
if field.Anonymous && isStruct(fieldValue.Type()) {
- newErrs := unpackStructValue(namePrefix, fieldValue, propertyMap)
- errs = append(errs, newErrs...)
+ ctx.unpackToStruct(namePrefix, fieldValue)
continue
}
@@ -220,60 +265,125 @@
continue
}
- packedProperty.unpacked = true
+ packedProperty.used = true
+ property := packedProperty.property
if HasTag(field, "blueprint", "mutated") {
- errs = append(errs,
+ if !ctx.addError(
&UnpackError{
- Err: fmt.Errorf("mutated field %s cannot be set in a Blueprint file", propertyName),
- Pos: packedProperty.property.ColonPos,
- })
- if len(errs) >= maxUnpackErrors {
- return errs
+ fmt.Errorf("mutated field %s cannot be set in a Blueprint file", propertyName),
+ property.ColonPos,
+ }) {
+ return
}
continue
}
- var newErrs []error
-
if isStruct(fieldValue.Type()) {
- newErrs = unpackStruct(propertyName+".", fieldValue,
- packedProperty.property, propertyMap)
-
- errs = append(errs, newErrs...)
- if len(errs) >= maxUnpackErrors {
- return errs
+ ctx.unpackToStruct(propertyName, fieldValue)
+ if len(ctx.errs) >= maxUnpackErrors {
+ return
+ }
+ } else if isSlice(fieldValue.Type()) {
+ if unpackedValue, ok := ctx.unpackToSlice(propertyName, property, fieldValue.Type()); ok {
+ ExtendBasicType(fieldValue, unpackedValue, Append)
+ }
+ if len(ctx.errs) >= maxUnpackErrors {
+ return
}
- continue
- }
-
- // Handle basic types and pointers to basic types
-
- propertyValue, err := propertyToValue(fieldValue.Type(), packedProperty.property)
- if err != nil {
- errs = append(errs, err)
- if len(errs) >= maxUnpackErrors {
- return errs
+ } else {
+ unpackedValue, err := propertyToValue(fieldValue.Type(), property)
+ if err != nil && !ctx.addError(err) {
+ return
}
+ ExtendBasicType(fieldValue, unpackedValue, Append)
}
-
- ExtendBasicType(fieldValue, propertyValue, Append)
}
-
- return errs
}
-func propertyToValue(typ reflect.Type, property *parser.Property) (reflect.Value, error) {
- var value reflect.Value
-
- var ptr bool
- if typ.Kind() == reflect.Ptr {
- typ = typ.Elem()
- ptr = true
+// unpackSlice creates a value of a given slice type from the property which should be a list
+func (ctx *unpackContext) unpackToSlice(
+ sliceName string, property *parser.Property, sliceType reflect.Type) (reflect.Value, bool) {
+ propValueAsList, ok := property.Value.Eval().(*parser.List)
+ if !ok {
+ ctx.addError(fmt.Errorf("%s: can't assign %s value to list property %q",
+ property.Value.Pos(), property.Value.Type(), property.Name))
+ return reflect.MakeSlice(sliceType, 0, 0), false
+ }
+ exprs := propValueAsList.Values
+ value := reflect.MakeSlice(sliceType, 0, len(exprs))
+ if len(exprs) == 0 {
+ return value, true
}
- switch kind := typ.Kind(); kind {
+ // The function to construct an item value depends on the type of list elements.
+ var getItemFunc func(*parser.Property, reflect.Type) (reflect.Value, bool)
+ switch exprs[0].Type() {
+ case parser.BoolType, parser.StringType, parser.Int64Type:
+ getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
+ value, err := propertyToValue(t, property)
+ if err != nil {
+ ctx.addError(err)
+ return value, false
+ }
+ return value, true
+ }
+ case parser.ListType:
+ getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
+ return ctx.unpackToSlice(property.Name, property, t)
+ }
+ case parser.MapType:
+ getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
+ itemValue := reflect.New(t).Elem()
+ ctx.unpackToStruct(property.Name, itemValue)
+ return itemValue, true
+ }
+ case parser.NotEvaluatedType:
+ getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
+ return reflect.New(t), false
+ }
+ default:
+ panic(fmt.Errorf("bizarre property expression type: %v", exprs[0].Type()))
+ }
+
+ itemProperty := &parser.Property{NamePos: property.NamePos, ColonPos: property.ColonPos}
+ elemType := sliceType.Elem()
+ isPtr := elemType.Kind() == reflect.Ptr
+
+ for i, expr := range exprs {
+ itemProperty.Name = sliceName + "[" + strconv.Itoa(i) + "]"
+ itemProperty.Value = expr
+ if packedProperty, ok := ctx.propertyMap[itemProperty.Name]; ok {
+ packedProperty.used = true
+ }
+ if isPtr {
+ if itemValue, ok := getItemFunc(itemProperty, elemType.Elem()); ok {
+ ptrValue := reflect.New(itemValue.Type())
+ ptrValue.Elem().Set(itemValue)
+ value = reflect.Append(value, ptrValue)
+ }
+ } else {
+ if itemValue, ok := getItemFunc(itemProperty, elemType); ok {
+ value = reflect.Append(value, itemValue)
+ }
+ }
+ }
+ return value, true
+}
+
+// propertyToValue creates a value of a given value type from the property.
+func propertyToValue(typ reflect.Type, property *parser.Property) (reflect.Value, error) {
+ var value reflect.Value
+ var baseType reflect.Type
+ isPtr := typ.Kind() == reflect.Ptr
+ if isPtr {
+ baseType = typ.Elem()
+ } else {
+ baseType = typ
+ }
+
+ switch kind := baseType.Kind(); kind {
case reflect.Bool:
b, ok := property.Value.Eval().(*parser.Bool)
if !ok {
@@ -298,53 +408,16 @@
}
value = reflect.ValueOf(s.Value)
- case reflect.Slice:
- l, ok := property.Value.Eval().(*parser.List)
- if !ok {
- return value, fmt.Errorf("%s: can't assign %s value to list property %q",
- property.Value.Pos(), property.Value.Type(), property.Name)
- }
-
- list := make([]string, len(l.Values))
- for i, value := range l.Values {
- s, ok := value.Eval().(*parser.String)
- if !ok {
- // The parser should not produce this.
- panic(fmt.Errorf("non-string value %q found in list", value))
- }
- list[i] = s.Value
- }
-
- value = reflect.ValueOf(list)
-
default:
- panic(fmt.Errorf("unexpected kind %s", kind))
+ return value, &UnpackError{
+ fmt.Errorf("cannot assign %s value %s to %s property %s", property.Value.Type(), property.Value, kind, typ),
+ property.NamePos}
}
- if ptr {
+ if isPtr {
ptrValue := reflect.New(value.Type())
ptrValue.Elem().Set(value)
- value = ptrValue
+ return ptrValue, nil
}
-
return value, nil
}
-
-func unpackStruct(namePrefix string, structValue reflect.Value,
- property *parser.Property, propertyMap map[string]*packedProperty) []error {
-
- m, ok := property.Value.Eval().(*parser.Map)
- if !ok {
- return []error{
- fmt.Errorf("%s: can't assign %s value to map property %q",
- property.Value.Pos(), property.Value.Type(), property.Name),
- }
- }
-
- errs := buildPropertyMap(namePrefix, m.Properties, propertyMap)
- if len(errs) > 0 {
- return errs
- }
-
- return unpackStructValue(namePrefix, structValue, propertyMap)
-}
diff --git a/proptools/unpack_test.go b/proptools/unpack_test.go
index 942dbb8..6a4d7b4 100644
--- a/proptools/unpack_test.go
+++ b/proptools/unpack_test.go
@@ -17,8 +17,8 @@
import (
"bytes"
"reflect"
+
"testing"
- "text/scanner"
"github.com/google/blueprint/parser"
)
@@ -218,6 +218,144 @@
},
},
+ // List of maps
+ {
+ input: `
+ m {
+ mapslist: [
+ {
+ foo: "abc",
+ bar: true,
+ },
+ {
+ foo: "def",
+ bar: false,
+ }
+ ],
+ }
+ `,
+ output: []interface{}{
+ &struct {
+ Mapslist []struct {
+ Foo string
+ Bar bool
+ }
+ }{
+ Mapslist: []struct {
+ Foo string
+ Bar bool
+ }{
+ {Foo: "abc", Bar: true},
+ {Foo: "def", Bar: false},
+ },
+ },
+ },
+ },
+
+ // List of pointers to structs
+ {
+ input: `
+ m {
+ mapslist: [
+ {
+ foo: "abc",
+ bar: true,
+ },
+ {
+ foo: "def",
+ bar: false,
+ }
+ ],
+ }
+ `,
+ output: []interface{}{
+ &struct {
+ Mapslist []*struct {
+ Foo string
+ Bar bool
+ }
+ }{
+ Mapslist: []*struct {
+ Foo string
+ Bar bool
+ }{
+ {Foo: "abc", Bar: true},
+ {Foo: "def", Bar: false},
+ },
+ },
+ },
+ },
+
+ // List of lists
+ {
+ input: `
+ m {
+ listoflists: [
+ ["abc",],
+ ["def",],
+ ],
+ }
+ `,
+ output: []interface{}{
+ &struct {
+ Listoflists [][]string
+ }{
+ Listoflists: [][]string{
+ []string{"abc"},
+ []string{"def"},
+ },
+ },
+ },
+ },
+
+ // Multilevel
+ {
+ input: `
+ m {
+ name: "mymodule",
+ flag: true,
+ settings: ["foo1", "foo2", "foo3",],
+ perarch: {
+ arm: "32",
+ arm64: "64",
+ },
+ configvars: [
+ { var: "var1", values: ["1.1", "1.2", ], },
+ { var: "var2", values: ["2.1", ], },
+ ],
+ }
+ `,
+ output: []interface{}{
+ &struct {
+ Name string
+ Flag bool
+ Settings []string
+ Perarch *struct {
+ Arm string
+ Arm64 string
+ }
+ Configvars []struct {
+ Var string
+ Values []string
+ }
+ }{
+ Name: "mymodule",
+ Flag: true,
+ Settings: []string{"foo1", "foo2", "foo3"},
+ Perarch: &struct {
+ Arm string
+ Arm64 string
+ }{Arm: "32", Arm64: "64"},
+ Configvars: []struct {
+ Var string
+ Values []string
+ }{
+ {Var: "var1", Values: []string{"1.1", "1.2"}},
+ {Var: "var2", Values: []string{"2.1"}},
+ },
+ },
+ },
+ },
// Anonymous struct
{
input: `
@@ -358,21 +496,31 @@
list = ["abc"]
string = "def"
list_with_variable = [string]
+ struct_value = { name: "foo" }
m {
s: string,
list: list,
list2: list_with_variable,
+ structattr: struct_value,
}
`,
output: []interface{}{
&struct {
- S string
- List []string
- List2 []string
+ S string
+ List []string
+ List2 []string
+ Structattr struct {
+ Name string
+ }
}{
S: "def",
List: []string{"abc"},
List2: []string{"def"},
+ Structattr: struct {
+ Name string
+ }{
+ Name: "foo",
+ },
},
},
},
@@ -585,10 +733,185 @@
}
}
-func mkpos(offset, line, column int) scanner.Position {
- return scanner.Position{
- Offset: offset,
- Line: line,
- Column: column,
+func BenchmarkUnpackProperties(b *testing.B) {
+ run := func(b *testing.B, props []interface{}, input string) {
+ b.ReportAllocs()
+ b.StopTimer()
+ r := bytes.NewBufferString(input)
+ file, errs := parser.ParseAndEval("", r, parser.NewScope(nil))
+ if len(errs) != 0 {
+ b.Errorf("test case: %s", input)
+ b.Errorf("unexpected parse errors:")
+ for _, err := range errs {
+ b.Errorf(" %s", err)
+ }
+ b.FailNow()
+ }
+
+ for i := 0; i < b.N; i++ {
+ for _, def := range file.Defs {
+ module, ok := def.(*parser.Module)
+ if !ok {
+ continue
+ }
+
+ var output []interface{}
+ for _, p := range props {
+ output = append(output, CloneProperties(reflect.ValueOf(p)).Interface())
+ }
+
+ b.StartTimer()
+ _, errs = UnpackProperties(module.Properties, output...)
+ b.StopTimer()
+ if len(errs) > 0 {
+ b.Errorf("unexpected unpack errors:")
+ for _, err := range errs {
+ b.Errorf(" %s", err)
+ }
+ }
+ }
+ }
}
+
+ b.Run("basic", func(b *testing.B) {
+ props := []interface{}{
+ &struct {
+ Nested struct {
+ S string
+ }
+ }{},
+ }
+ bp := `
+ m {
+ nested: {
+ s: "abc",
+ },
+ }
+ `
+ run(b, props, bp)
+ })
+
+ b.Run("interface", func(b *testing.B) {
+ props := []interface{}{
+ &struct {
+ Nested interface{}
+ }{
+ Nested: (*struct {
+ S string
+ })(nil),
+ },
+ }
+ bp := `
+ m {
+ nested: {
+ s: "abc",
+ },
+ }
+ `
+ run(b, props, bp)
+ })
+
+ b.Run("many", func(b *testing.B) {
+ props := []interface{}{
+ &struct {
+ A *string
+ B *string
+ C *string
+ D *string
+ E *string
+ F *string
+ G *string
+ H *string
+ I *string
+ J *string
+ }{},
+ }
+ bp := `
+ m {
+ a: "a",
+ b: "b",
+ c: "c",
+ d: "d",
+ e: "e",
+ f: "f",
+ g: "g",
+ h: "h",
+ i: "i",
+ j: "j",
+ }
+ `
+ run(b, props, bp)
+ })
+
+ b.Run("deep", func(b *testing.B) {
+ props := []interface{}{
+ &struct {
+ Nested struct {
+ Nested struct {
+ Nested struct {
+ Nested struct {
+ Nested struct {
+ Nested struct {
+ Nested struct {
+ Nested struct {
+ Nested struct {
+ Nested struct {
+ S string
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }{},
+ }
+ bp := `
+ m {
+ nested: { nested: { nested: { nested: { nested: {
+ nested: { nested: { nested: { nested: { nested: {
+ s: "abc",
+ }, }, }, }, },
+ }, }, }, }, },
+ }
+ `
+ run(b, props, bp)
+ })
+
+ b.Run("mix", func(b *testing.B) {
+ props := []interface{}{
+ &struct {
+ Name string
+ Flag bool
+ Settings []string
+ Perarch *struct {
+ Arm string
+ Arm64 string
+ }
+ Configvars []struct {
+ Name string
+ Values []string
+ }
+ }{},
+ }
+ bp := `
+ m {
+ name: "mymodule",
+ flag: true,
+ settings: ["foo1", "foo2", "foo3",],
+ perarch: {
+ arm: "32",
+ arm64: "64",
+ },
+ configvars: [
+ { name: "var1", values: ["var1:1", "var1:2", ], },
+ { name: "var2", values: ["var2:1", "var2:2", ], },
+ ],
+ }
+ `
+ run(b, props, bp)
+ })
}