diff --git a/Android.bp b/Android.bp index ee0ede0..9fc62e6 100644 --- a/Android.bp +++ b/Android.bp @@ -133,6 +133,7 @@ bootstrap_go_package { ], testSrcs: [ "proptools/clone_test.go", + "proptools/configurable_test.go", "proptools/escape_test.go", "proptools/extend_test.go", "proptools/filter_test.go", diff --git a/proptools/configurable.go b/proptools/configurable.go index 980ee47..4dfe007 100644 --- a/proptools/configurable.go +++ b/proptools/configurable.go @@ -412,6 +412,22 @@ type Configurable[T ConfigurableElements] struct { marker configurableMarker propertyName string inner *configurableInner[T] + // See Configurable.evaluate for a description of the postProcessor algorithm and + // why this is a 2d list + postProcessors *[][]postProcessor[T] +} + +type postProcessor[T ConfigurableElements] struct { + f func(T) T + // start and end represent the range of configurableInners + // that this postprocessor is applied to. When appending two configurables + // together, the start and end values will stay the same for the left + // configurable's postprocessors, but the rights will be rebased by the + // number of configurableInners in the left configurable. This way + // the postProcessors still only apply to the configurableInners they + // origionally applied to before the appending. + start int + end int } type configurableInner[T ConfigurableElements] struct { @@ -440,6 +456,7 @@ func NewConfigurable[T ConfigurableElements](conditions []ConfigurableCondition, // Clone the slices so they can't be modified from soong conditions = slices.Clone(conditions) cases = slices.Clone(cases) + var zeroPostProcessors [][]postProcessor[T] return Configurable[T]{ inner: &configurableInner[T]{ single: singleConfigurable[T]{ @@ -447,35 +464,77 @@ func NewConfigurable[T ConfigurableElements](conditions []ConfigurableCondition, cases: cases, }, }, + postProcessors: &zeroPostProcessors, } } +func newConfigurableWithPropertyName[T ConfigurableElements](propertyName string, conditions []ConfigurableCondition, cases []ConfigurableCase[T], addScope bool) Configurable[T] { + result := NewConfigurable(conditions, cases) + result.propertyName = propertyName + if addScope { + for curr := result.inner; curr != nil; curr = curr.next { + curr.single.scope = parser.NewScope(nil) + } + } + return result +} + func (c *Configurable[T]) AppendSimpleValue(value T) { value = copyConfiguredValue(value) // This may be a property that was never initialized from a bp file if c.inner == nil { - c.inner = &configurableInner[T]{ - single: singleConfigurable[T]{ - cases: []ConfigurableCase[T]{{ - value: configuredValueToExpression(value), - }}, - }, - } + c.initialize(nil, "", nil, []ConfigurableCase[T]{{ + value: configuredValueToExpression(value), + }}) return } c.inner.appendSimpleValue(value) } +// AddPostProcessor adds a function that will modify the result of +// Get() when Get() is called. It operates on all the current contents +// of the Configurable property, but if other values are appended to +// the Configurable property afterwards, the postProcessor will not run +// on them. This can be useful to essentially modify a configurable +// property without evaluating it. +func (c *Configurable[T]) AddPostProcessor(p func(T) T) { + // Add the new postProcessor on top of the tallest stack of postProcessors. + // See Configurable.evaluate for more details on the postProcessors algorithm + // and data structure. + num_links := c.inner.numLinks() + if c.postProcessors == nil || len(*c.postProcessors) == 0 { + c.postProcessors = &[][]postProcessor[T]{{{ + f: p, + start: 0, + end: num_links, + }}} + } else { + deepestI := 0 + deepestDepth := 0 + for i := 0; i < len(*c.postProcessors); i++ { + if len((*c.postProcessors)[i]) > deepestDepth { + deepestDepth = len((*c.postProcessors)[i]) + deepestI = i + } + } + (*c.postProcessors)[deepestI] = append((*c.postProcessors)[deepestI], postProcessor[T]{ + f: p, + start: 0, + end: num_links, + }) + } +} + // Get returns the final value for the configurable property. // A configurable property may be unset, in which case Get will return nil. func (c *Configurable[T]) Get(evaluator ConfigurableEvaluator) ConfigurableOptional[T] { - result := c.inner.evaluate(c.propertyName, evaluator) + result := c.evaluate(c.propertyName, evaluator) return configuredValuePtrToOptional(result) } // GetOrDefault is the same as Get, but will return the provided default value if the property was unset. func (c *Configurable[T]) GetOrDefault(evaluator ConfigurableEvaluator, defaultValue T) T { - result := c.inner.evaluate(c.propertyName, evaluator) + result := c.evaluate(c.propertyName, evaluator) if result != nil { // Copy the result so that it can't be changed from soong return copyConfiguredValue(*result) @@ -483,6 +542,127 @@ func (c *Configurable[T]) GetOrDefault(evaluator ConfigurableEvaluator, defaultV return defaultValue } +type valueAndIndices[T ConfigurableElements] struct { + value *T + replace bool + // Similar to start/end in postProcessor, these represent the origional + // range or configurableInners that this merged group represents. It's needed + // in order to apply recursive postProcessors to only the relevant + // configurableInners, even after those configurableInners have been merged + // in order to apply an earlier postProcessor. + start int + end int +} + +func (c *Configurable[T]) evaluate(propertyName string, evaluator ConfigurableEvaluator) *T { + if c.inner == nil { + return nil + } + + if len(*c.postProcessors) == 0 { + // Use a simpler algorithm if there are no postprocessors + return c.inner.evaluate(propertyName, evaluator) + } + + // The basic idea around evaluating with postprocessors is that each individual + // node in the chain (each configurableInner) is first evaluated, and then when + // a postprocessor operates on a certain range, that range is merged before passing + // it to the postprocessor. We want postProcessors to only accept a final merged + // value instead of a linked list, but at the same time, only operate over a portion + // of the list. If more configurables are appended onto this one, their values won't + // be operated on by the existing postProcessors, but they may have their own + // postprocessors. + // + // _____________________ + // | __________| + // ______ | _____| ___ + // | | | | | | + // a -> b -> c -> d -> e -> f -> g + // + // In this diagram, the letters along the bottom is the chain of configurableInners. + // The brackets on top represent postprocessors, where higher brackets are processed + // after lower ones. + // + // To evaluate this example, first we evaluate the raw values for all nodes a->g. + // Then we merge nodes a/b and d/e and apply the postprocessors to their merged values, + // and also to g. Those merged and postprocessed nodes are then reinserted into the + // list, and we move on to doing the higher level postprocessors (starting with the c->e one) + // in the same way. When all postprocessors are done, a final merge is done on anything + // leftover. + // + // The Configurable.postProcessors field is a 2d array to represent this hierarchy. + // The outer index moves right on this graph, the inner index goes up. + // When adding a new postProcessor, it will always be the last postProcessor to run + // until another is added or another configurable is appended. So in AddPostProcessor(), + // we add it to the tallest existing stack. + + var currentValues []valueAndIndices[T] + for curr, i := c.inner, 0; curr != nil; curr, i = curr.next, i+1 { + value := curr.single.evaluateNonTransitive(propertyName, evaluator) + currentValues = append(currentValues, valueAndIndices[T]{ + value: value, + replace: curr.replace, + start: i, + end: i + 1, + }) + } + + if c.postProcessors == nil || len(*c.postProcessors) == 0 { + return mergeValues(currentValues).value + } + + foundPostProcessor := true + for depth := 0; foundPostProcessor; depth++ { + foundPostProcessor = false + var newValues []valueAndIndices[T] + i := 0 + for _, postProcessorGroup := range *c.postProcessors { + if len(postProcessorGroup) > depth { + foundPostProcessor = true + postProcessor := postProcessorGroup[depth] + startI := 0 + endI := 0 + for currentValues[startI].start < postProcessor.start { + startI++ + } + for currentValues[endI].end < postProcessor.end { + endI++ + } + endI++ + newValues = append(newValues, currentValues[i:startI]...) + merged := mergeValues(currentValues[startI:endI]) + if merged.value != nil { + processed := postProcessor.f(*merged.value) + merged.value = &processed + } + newValues = append(newValues, merged) + i = endI + } + } + newValues = append(newValues, currentValues[i:]...) + currentValues = newValues + } + + return mergeValues(currentValues).value +} + +func mergeValues[T ConfigurableElements](values []valueAndIndices[T]) valueAndIndices[T] { + if len(values) < 0 { + panic("Expected at least 1 value in mergeValues") + } + result := values[0] + for i := 1; i < len(values); i++ { + if result.replace { + result.value = replaceConfiguredValues(result.value, values[i].value) + } else { + result.value = appendConfiguredValues(result.value, values[i].value) + } + result.end = values[i].end + result.replace = values[i].replace + } + return result +} + func (c *configurableInner[T]) evaluate(propertyName string, evaluator ConfigurableEvaluator) *T { if c == nil { return nil @@ -668,6 +848,12 @@ func (c *Configurable[T]) initialize(scope *parser.Scope, propertyName string, c scope: scope, }, } + var postProcessors [][]postProcessor[T] + c.postProcessors = &postProcessors +} + +func (c *Configurable[T]) Append(other Configurable[T]) { + c.setAppend(other, false, false) } func (c Configurable[T]) setAppend(append any, replace bool, prepend bool) { @@ -675,12 +861,37 @@ func (c Configurable[T]) setAppend(append any, replace bool, prepend bool) { if a.inner.isEmpty() { return } + + if prepend { + newBase := a.inner.numLinks() + *c.postProcessors = appendPostprocessors(*a.postProcessors, *c.postProcessors, newBase) + } else { + newBase := c.inner.numLinks() + *c.postProcessors = appendPostprocessors(*c.postProcessors, *a.postProcessors, newBase) + } + c.inner.setAppend(a.inner, replace, prepend) if c.inner == c.inner.next { panic("pointer loop") } } +func appendPostprocessors[T ConfigurableElements](a, b [][]postProcessor[T], newBase int) [][]postProcessor[T] { + var result [][]postProcessor[T] + for i := 0; i < len(a); i++ { + result = append(result, slices.Clone(a[i])) + } + for i := 0; i < len(b); i++ { + n := slices.Clone(b[i]) + for j := 0; j < len(n); j++ { + n[j].start += newBase + n[j].end += newBase + } + result = append(result, n) + } + return result +} + func (c *configurableInner[T]) setAppend(append *configurableInner[T], replace bool, prepend bool) { if c.isEmpty() { *c = *append.clone() @@ -719,6 +930,14 @@ func (c *configurableInner[T]) setAppend(append *configurableInner[T], replace b } } +func (c *configurableInner[T]) numLinks() int { + result := 0 + for curr := c; curr != nil; curr = curr.next { + result++ + } + return result +} + func (c *configurableInner[T]) appendSimpleValue(value T) { if c.next == nil { c.replace = false @@ -761,10 +980,20 @@ func (c *singleConfigurable[T]) printfInto(value string) error { } func (c Configurable[T]) clone() any { - return Configurable[T]{ - propertyName: c.propertyName, - inner: c.inner.clone(), + var newPostProcessors *[][]postProcessor[T] + if c.postProcessors != nil { + x := appendPostprocessors(*c.postProcessors, nil, 0) + newPostProcessors = &x } + return Configurable[T]{ + propertyName: c.propertyName, + inner: c.inner.clone(), + postProcessors: newPostProcessors, + } +} + +func (c Configurable[T]) Clone() Configurable[T] { + return c.clone().(Configurable[T]) } func (c *configurableInner[T]) clone() *configurableInner[T] { @@ -973,6 +1202,7 @@ func promoteValueToConfigurable(origional reflect.Value) reflect.Value { }}, }, }, + postProcessors: &[][]postProcessor[string]{}, }) case reflect.Bool: return reflect.ValueOf(Configurable[bool]{ @@ -983,6 +1213,7 @@ func promoteValueToConfigurable(origional reflect.Value) reflect.Value { }}, }, }, + postProcessors: &[][]postProcessor[bool]{}, }) case reflect.Slice: return reflect.ValueOf(Configurable[[]string]{ @@ -993,6 +1224,7 @@ func promoteValueToConfigurable(origional reflect.Value) reflect.Value { }}, }, }, + postProcessors: &[][]postProcessor[[]string]{}, }) default: panic(fmt.Sprintf("Can't convert %s property to a configurable", origional.Kind().String())) diff --git a/proptools/configurable_test.go b/proptools/configurable_test.go new file mode 100644 index 0000000..f3ea417 --- /dev/null +++ b/proptools/configurable_test.go @@ -0,0 +1,77 @@ +package proptools + +import ( + "fmt" + "reflect" + "testing" +) + +func TestPostProcessor(t *testing.T) { + // Same as the ascii art example in Configurable.evaluate() + prop := NewConfigurable[[]string](nil, nil) + prop.AppendSimpleValue([]string{"a"}) + prop.AppendSimpleValue([]string{"b"}) + prop.AddPostProcessor(addToElements("1")) + + prop2 := NewConfigurable[[]string](nil, nil) + prop2.AppendSimpleValue([]string{"c"}) + + prop3 := NewConfigurable[[]string](nil, nil) + prop3.AppendSimpleValue([]string{"d"}) + prop3.AppendSimpleValue([]string{"e"}) + prop3.AddPostProcessor(addToElements("2")) + + prop4 := NewConfigurable[[]string](nil, nil) + prop4.AppendSimpleValue([]string{"f"}) + + prop5 := NewConfigurable[[]string](nil, nil) + prop5.AppendSimpleValue([]string{"g"}) + prop5.AddPostProcessor(addToElements("3")) + + prop2.Append(prop3) + prop2.AddPostProcessor(addToElements("z")) + + prop.Append(prop2) + prop.AddPostProcessor(addToElements("y")) + prop.Append(prop4) + prop.Append(prop5) + + expected := []string{"a1y", "b1y", "czy", "d2zy", "e2zy", "f", "g3"} + x := prop.Get(&configurableEvalutorForTesting{}) + if !reflect.DeepEqual(x.Get(), expected) { + t.Fatalf("Expected %v, got %v", expected, x.Get()) + } +} + +func addToElements(s string) func([]string) []string { + return func(arr []string) []string { + for i := range arr { + arr[i] = arr[i] + s + } + return arr + } +} + +type configurableEvalutorForTesting struct { + vars map[string]string +} + +func (e *configurableEvalutorForTesting) EvaluateConfiguration(condition ConfigurableCondition, property string) ConfigurableValue { + if condition.functionName != "f" { + panic("Expected functionName to be f") + } + if len(condition.args) != 1 { + panic("Expected exactly 1 arg") + } + val, ok := e.vars[condition.args[0]] + if ok { + return ConfigurableValueString(val) + } + return ConfigurableValueUndefined() +} + +func (e *configurableEvalutorForTesting) PropertyErrorf(property, fmtString string, args ...interface{}) { + panic(fmt.Sprintf(fmtString, args...)) +} + +var _ ConfigurableEvaluator = (*configurableEvalutorForTesting)(nil) diff --git a/proptools/extend_test.go b/proptools/extend_test.go index 0148204..4fabf52 100644 --- a/proptools/extend_test.go +++ b/proptools/extend_test.go @@ -1259,196 +1259,166 @@ func appendPropertiesTestCases() []appendPropertyTestCase { { name: "Append configurable", dst: &struct{ S Configurable[[]string] }{ - S: Configurable[[]string]{ - inner: &configurableInner[[]string]{ - single: singleConfigurable[[]string]{ - conditions: []ConfigurableCondition{{ - functionName: "soong_config_variable", - args: []string{ - "my_namespace", - "foo", - }, - }}, - cases: []ConfigurableCase[[]string]{{ - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "a", - }}, - value: &parser.List{Values: []parser.Expression{ - &parser.String{Value: "1"}, - &parser.String{Value: "2"}, - }}, - }}, - }, + S: NewConfigurable[[]string]([]ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "foo", }, - }, + }}, + []ConfigurableCase[[]string]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", + }}, + value: &parser.List{Values: []parser.Expression{ + &parser.String{Value: "1"}, + &parser.String{Value: "2"}, + }}, + }}, + ), }, src: &struct{ S Configurable[[]string] }{ - S: Configurable[[]string]{ - inner: &configurableInner[[]string]{ - single: singleConfigurable[[]string]{ - conditions: []ConfigurableCondition{{ - functionName: "release_variable", - args: []string{ - "bar", - }, - }}, - cases: []ConfigurableCase[[]string]{{ - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "b", - }}, - value: &parser.List{Values: []parser.Expression{ - &parser.String{Value: "3"}, - &parser.String{Value: "4"}, - }}, - }}, - }, + S: NewConfigurable([]ConfigurableCondition{{ + functionName: "release_variable", + args: []string{ + "bar", }, - }, + }}, + []ConfigurableCase[[]string]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "b", + }}, + value: &parser.List{Values: []parser.Expression{ + &parser.String{Value: "3"}, + &parser.String{Value: "4"}, + }}, + }}, + ), }, out: &struct{ S Configurable[[]string] }{ - S: Configurable[[]string]{ - inner: &configurableInner[[]string]{ - single: singleConfigurable[[]string]{ - conditions: []ConfigurableCondition{{ - functionName: "soong_config_variable", - args: []string{ - "my_namespace", - "foo", - }, - }}, - cases: []ConfigurableCase[[]string]{{ - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "a", - }}, - value: &parser.List{Values: []parser.Expression{ - &parser.String{Value: "1"}, - &parser.String{Value: "2"}, - }}, - }}, + S: func() Configurable[[]string] { + result := NewConfigurable([]ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "foo", }, - next: &configurableInner[[]string]{ - single: singleConfigurable[[]string]{ - conditions: []ConfigurableCondition{{ - functionName: "release_variable", - args: []string{ - "bar", - }, - }}, - cases: []ConfigurableCase[[]string]{{ - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "b", - }}, - value: &parser.List{Values: []parser.Expression{ - &parser.String{Value: "3"}, - &parser.String{Value: "4"}, - }}, - }}, - }, + }}, + []ConfigurableCase[[]string]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", + }}, + value: &parser.List{Values: []parser.Expression{ + &parser.String{Value: "1"}, + &parser.String{Value: "2"}, + }}, + }}, + ) + result.Append(NewConfigurable([]ConfigurableCondition{{ + functionName: "release_variable", + args: []string{ + "bar", }, - }, - }, + }}, + []ConfigurableCase[[]string]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "b", + }}, + value: &parser.List{Values: []parser.Expression{ + &parser.String{Value: "3"}, + &parser.String{Value: "4"}, + }}, + }})) + return result + }(), }, }, { name: "Prepend configurable", order: Prepend, dst: &struct{ S Configurable[[]string] }{ - S: Configurable[[]string]{ - inner: &configurableInner[[]string]{ - single: singleConfigurable[[]string]{ - conditions: []ConfigurableCondition{{ - functionName: "soong_config_variable", - args: []string{ - "my_namespace", - "foo", - }, - }}, - cases: []ConfigurableCase[[]string]{{ - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "a", - }}, - value: &parser.List{Values: []parser.Expression{ - &parser.String{Value: "1"}, - &parser.String{Value: "2"}, - }}, - }}, - }, + S: NewConfigurable([]ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "foo", }, - }, + }}, + []ConfigurableCase[[]string]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", + }}, + value: &parser.List{Values: []parser.Expression{ + &parser.String{Value: "1"}, + &parser.String{Value: "2"}, + }}, + }}, + ), }, src: &struct{ S Configurable[[]string] }{ - S: Configurable[[]string]{ - inner: &configurableInner[[]string]{ - single: singleConfigurable[[]string]{ - conditions: []ConfigurableCondition{{ - functionName: "release_variable", - args: []string{ - "bar", - }, - }}, - cases: []ConfigurableCase[[]string]{{ - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "b", - }}, - value: &parser.List{Values: []parser.Expression{ - &parser.String{Value: "3"}, - &parser.String{Value: "4"}, - }}, - }}, - }, + S: NewConfigurable([]ConfigurableCondition{{ + functionName: "release_variable", + args: []string{ + "bar", }, - }, + }}, + []ConfigurableCase[[]string]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "b", + }}, + value: &parser.List{Values: []parser.Expression{ + &parser.String{Value: "3"}, + &parser.String{Value: "4"}, + }}, + }}, + ), }, out: &struct{ S Configurable[[]string] }{ - S: Configurable[[]string]{ - inner: &configurableInner[[]string]{ - single: singleConfigurable[[]string]{ - conditions: []ConfigurableCondition{{ - functionName: "release_variable", - args: []string{ - "bar", - }, - }}, - cases: []ConfigurableCase[[]string]{{ - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "b", - }}, - value: &parser.List{Values: []parser.Expression{ - &parser.String{Value: "3"}, - &parser.String{Value: "4"}, - }}, - }}, - }, - next: &configurableInner[[]string]{ - single: singleConfigurable[[]string]{ - conditions: []ConfigurableCondition{{ - functionName: "soong_config_variable", - args: []string{ - "my_namespace", - "foo", - }, - }}, - cases: []ConfigurableCase[[]string]{{ - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "a", - }}, - value: &parser.List{Values: []parser.Expression{ - &parser.String{Value: "1"}, - &parser.String{Value: "2"}, - }}, - }}, + S: func() Configurable[[]string] { + result := NewConfigurable( + []ConfigurableCondition{{ + functionName: "release_variable", + args: []string{ + "bar", }, - }, - }, - }, + }}, + []ConfigurableCase[[]string]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "b", + }}, + value: &parser.List{Values: []parser.Expression{ + &parser.String{Value: "3"}, + &parser.String{Value: "4"}, + }}, + }}, + ) + result.Append(NewConfigurable( + []ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "foo", + }, + }}, + []ConfigurableCase[[]string]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", + }}, + value: &parser.List{Values: []parser.Expression{ + &parser.String{Value: "1"}, + &parser.String{Value: "2"}, + }}, + }})) + return result + }(), }, }, } @@ -1918,31 +1888,24 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase { order: Append, dst: []interface{}{ &struct{ S Configurable[bool] }{ - S: Configurable[bool]{ - inner: &configurableInner[bool]{ - single: singleConfigurable[bool]{ - conditions: []ConfigurableCondition{{ - functionName: "soong_config_variable", - args: []string{ - "my_namespace", - "foo", - }, - }}, - cases: []ConfigurableCase[bool]{{ - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "a", - }}, - value: &parser.Bool{Value: true}, - }, { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeDefault, - }}, - value: &parser.Bool{Value: false}, - }}, - }, + S: NewConfigurable[bool]([]ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "foo", }, - }, + }}, []ConfigurableCase[bool]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", + }}, + value: &parser.Bool{Value: true}, + }, { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeDefault, + }}, + value: &parser.Bool{Value: false}, + }}), }, }, src: &struct{ S *bool }{ @@ -1950,38 +1913,30 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase { }, out: []interface{}{ &struct{ S Configurable[bool] }{ - S: Configurable[bool]{ - inner: &configurableInner[bool]{ - single: singleConfigurable[bool]{ - conditions: []ConfigurableCondition{{ - functionName: "soong_config_variable", - args: []string{ - "my_namespace", - "foo", - }, - }}, - cases: []ConfigurableCase[bool]{{ - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "a", - }}, - value: &parser.Bool{Value: true}, - }, { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeDefault, - }}, - value: &parser.Bool{Value: false}, - }}, + S: func() Configurable[bool] { + result := NewConfigurable[bool]([]ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "foo", }, - next: &configurableInner[bool]{ - single: singleConfigurable[bool]{ - cases: []ConfigurableCase[bool]{{ - value: &parser.Bool{Value: true}, - }}, - }, - }, - }, - }, + }}, + []ConfigurableCase[bool]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", + }}, + value: &parser.Bool{Value: true}, + }, { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeDefault, + }}, + value: &parser.Bool{Value: false}, + }}, + ) + result.AppendSimpleValue(true) + return result + }(), }, }, }, @@ -1990,31 +1945,26 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase { order: Append, dst: []interface{}{ &struct{ S Configurable[bool] }{ - S: Configurable[bool]{ - inner: &configurableInner[bool]{ - single: singleConfigurable[bool]{ - conditions: []ConfigurableCondition{{ - functionName: "soong_config_variable", - args: []string{ - "my_namespace", - "foo", - }, - }}, - cases: []ConfigurableCase[bool]{{ - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "a", - }}, - value: &parser.Bool{Value: true}, - }, { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeDefault, - }}, - value: &parser.Bool{Value: false}, - }}, - }, + S: NewConfigurable[bool]([]ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "foo", }, - }, + }}, + []ConfigurableCase[bool]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", + }}, + value: &parser.Bool{Value: true}, + }, { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeDefault, + }}, + value: &parser.Bool{Value: false}, + }}, + ), }, }, src: &struct{ S bool }{ @@ -2022,38 +1972,31 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase { }, out: []interface{}{ &struct{ S Configurable[bool] }{ - S: Configurable[bool]{ - inner: &configurableInner[bool]{ - single: singleConfigurable[bool]{ - conditions: []ConfigurableCondition{{ - functionName: "soong_config_variable", - args: []string{ - "my_namespace", - "foo", - }, - }}, - cases: []ConfigurableCase[bool]{{ - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "a", - }}, - value: &parser.Bool{Value: true}, - }, { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeDefault, - }}, - value: &parser.Bool{Value: false}, - }}, - }, - next: &configurableInner[bool]{ - single: singleConfigurable[bool]{ - cases: []ConfigurableCase[bool]{{ - value: &parser.Bool{Value: true}, - }}, + S: func() Configurable[bool] { + result := NewConfigurable[bool]( + []ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "foo", }, - }, - }, - }, + }}, + []ConfigurableCase[bool]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", + }}, + value: &parser.Bool{Value: true}, + }, { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeDefault, + }}, + value: &parser.Bool{Value: false}, + }}, + ) + result.AppendSimpleValue(true) + return result + }(), }, }, }, diff --git a/proptools/unpack.go b/proptools/unpack.go index 91eae79..712e78c 100644 --- a/proptools/unpack.go +++ b/proptools/unpack.go @@ -354,6 +354,7 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa }) return reflect.New(configurableType), false } + var postProcessors [][]postProcessor[string] result := Configurable[string]{ propertyName: property.Name, inner: &configurableInner[string]{ @@ -363,6 +364,7 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa }}, }, }, + postProcessors: &postProcessors, } return reflect.ValueOf(&result), true case *parser.Bool: @@ -374,6 +376,7 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa }) return reflect.New(configurableType), false } + var postProcessors [][]postProcessor[bool] result := Configurable[bool]{ propertyName: property.Name, inner: &configurableInner[bool]{ @@ -383,6 +386,7 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa }}, }, }, + postProcessors: &postProcessors, } return reflect.ValueOf(&result), true case *parser.List: @@ -411,6 +415,7 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa value[i] = exprUnpacked.Interface().(string) } } + var postProcessors [][]postProcessor[[]string] result := Configurable[[]string]{ propertyName: property.Name, inner: &configurableInner[[]string]{ @@ -420,6 +425,7 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa }}, }, }, + postProcessors: &postProcessors, } return reflect.ValueOf(&result), true default: diff --git a/proptools/unpack_test.go b/proptools/unpack_test.go index 3af79fa..10ac0aa 100644 --- a/proptools/unpack_test.go +++ b/proptools/unpack_test.go @@ -733,23 +733,21 @@ var validUnpackTestCases = []struct { &struct { Foo Configurable[string] }{ - Foo: Configurable[string]{ - propertyName: "foo", - inner: &configurableInner[string]{ - single: singleConfigurable[string]{ - cases: []ConfigurableCase[string]{{ - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 17, - Line: 3, - Column: 10, - }, - Value: "bar", - }, - }}, + Foo: newConfigurableWithPropertyName( + "foo", + nil, + []ConfigurableCase[string]{{ + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 17, + Line: 3, + Column: 10, + }, + Value: "bar", }, - }, - }, + }}, + false, + ), }, }, }, @@ -764,24 +762,22 @@ var validUnpackTestCases = []struct { &struct { Foo Configurable[bool] }{ - Foo: Configurable[bool]{ - propertyName: "foo", - inner: &configurableInner[bool]{ - single: singleConfigurable[bool]{ - cases: []ConfigurableCase[bool]{{ - value: &parser.Bool{ - LiteralPos: scanner.Position{ - Offset: 17, - Line: 3, - Column: 10, - }, - Value: true, - Token: "true", - }, - }}, + Foo: newConfigurableWithPropertyName( + "foo", + nil, + []ConfigurableCase[bool]{{ + value: &parser.Bool{ + LiteralPos: scanner.Position{ + Offset: 17, + Line: 3, + Column: 10, + }, + Value: true, + Token: "true", }, - }, - }, + }}, + false, + ), }, }, }, @@ -796,45 +792,43 @@ var validUnpackTestCases = []struct { &struct { Foo Configurable[[]string] }{ - Foo: Configurable[[]string]{ - propertyName: "foo", - inner: &configurableInner[[]string]{ - single: singleConfigurable[[]string]{ - cases: []ConfigurableCase[[]string]{{ - value: &parser.List{ - LBracePos: scanner.Position{ - Offset: 17, + Foo: newConfigurableWithPropertyName( + "foo", + nil, + []ConfigurableCase[[]string]{{ + value: &parser.List{ + LBracePos: scanner.Position{ + Offset: 17, + Line: 3, + Column: 10, + }, + RBracePos: scanner.Position{ + Offset: 26, + Line: 3, + Column: 19, + }, + Values: []parser.Expression{ + &parser.String{ + LiteralPos: scanner.Position{ + Offset: 18, Line: 3, - Column: 10, - }, - RBracePos: scanner.Position{ - Offset: 26, - Line: 3, - Column: 19, - }, - Values: []parser.Expression{ - &parser.String{ - LiteralPos: scanner.Position{ - Offset: 18, - Line: 3, - Column: 11, - }, - Value: "a", - }, - &parser.String{ - LiteralPos: scanner.Position{ - Offset: 23, - Line: 3, - Column: 16, - }, - Value: "b", - }, + Column: 11, }, + Value: "a", }, - }}, + &parser.String{ + LiteralPos: scanner.Position{ + Offset: 23, + Line: 3, + Column: 16, + }, + Value: "b", + }, + }, }, - }, - }, + }}, + false, + ), }, }, }, @@ -853,64 +847,60 @@ var validUnpackTestCases = []struct { &struct { Foo Configurable[string] }{ - Foo: Configurable[string]{ - propertyName: "foo", - inner: &configurableInner[string]{ - single: singleConfigurable[string]{ - scope: parser.NewScope(nil), - conditions: []ConfigurableCondition{{ - functionName: "soong_config_variable", - args: []string{ - "my_namespace", - "my_variable", - }, + Foo: newConfigurableWithPropertyName( + "foo", + []ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "my_variable", + }, + }}, + []ConfigurableCase[string]{ + { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", }}, - cases: []ConfigurableCase[string]{ - { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "a", - }}, - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 90, - Line: 4, - Column: 11, - }, - Value: "a2", - }, + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 90, + Line: 4, + Column: 11, }, - { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "b", - }}, - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 106, - Line: 5, - Column: 11, - }, - Value: "b2", - }, + Value: "a2", + }, + }, + { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "b", + }}, + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 106, + Line: 5, + Column: 11, }, - { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeDefault, - }}, - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 126, - Line: 6, - Column: 15, - }, - Value: "c2", - }, + Value: "b2", + }, + }, + { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeDefault, + }}, + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 126, + Line: 6, + Column: 15, }, + Value: "c2", }, }, }, - }, + true, + ), }, }, }, @@ -933,119 +923,117 @@ var validUnpackTestCases = []struct { &struct { Foo Configurable[string] }{ - Foo: Configurable[string]{ - propertyName: "foo", - inner: &configurableInner[string]{ - single: singleConfigurable[string]{ - scope: parser.NewScope(nil), - conditions: []ConfigurableCondition{{ - functionName: "soong_config_variable", - args: []string{ - "my_namespace", - "my_variable", - }, - }}, - cases: []ConfigurableCase[string]{ - { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "a", - }}, - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 90, - Line: 4, - Column: 11, - }, - Value: "a2", - }, - }, - { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "b", - }}, - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 106, - Line: 5, - Column: 11, - }, - Value: "b2", - }, - }, - { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeDefault, - }}, - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 126, - Line: 6, - Column: 15, - }, - Value: "c2", - }, - }, + Foo: func() Configurable[string] { + result := newConfigurableWithPropertyName( + "foo", + []ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "my_variable", }, - }, - next: &configurableInner[string]{ - single: singleConfigurable[string]{ - scope: parser.NewScope(nil), - conditions: []ConfigurableCondition{{ - functionName: "soong_config_variable", - args: []string{ - "my_namespace", - "my_2nd_variable", - }, + }}, + []ConfigurableCase[string]{ + { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", }}, - cases: []ConfigurableCase[string]{ - { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "d", - }}, - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 218, - Line: 8, - Column: 11, - }, - Value: "d2", - }, + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 90, + Line: 4, + Column: 11, }, - { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "e", - }}, - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 234, - Line: 9, - Column: 11, - }, - Value: "e2", - }, + Value: "a2", + }, + }, + { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "b", + }}, + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 106, + Line: 5, + Column: 11, }, - { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeDefault, - }}, - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 254, - Line: 10, - Column: 15, - }, - Value: "f2", - }, + Value: "b2", + }, + }, + { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeDefault, + }}, + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 126, + Line: 6, + Column: 15, }, + Value: "c2", }, }, }, - }, - }, + true, + ) + result.Append(newConfigurableWithPropertyName( + "", + []ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "my_2nd_variable", + }, + }}, + []ConfigurableCase[string]{ + { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "d", + }}, + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 218, + Line: 8, + Column: 11, + }, + Value: "d2", + }, + }, + { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "e", + }}, + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 234, + Line: 9, + Column: 11, + }, + Value: "e2", + }, + }, + { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeDefault, + }}, + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 254, + Line: 10, + Column: 15, + }, + Value: "f2", + }, + }, + }, + true, + )) + return result + }(), }, }, }, @@ -1064,41 +1052,37 @@ var validUnpackTestCases = []struct { Foo Configurable[string] Bar Configurable[bool] }{ - Foo: Configurable[string]{ - propertyName: "foo", - inner: &configurableInner[string]{ - single: singleConfigurable[string]{ - cases: []ConfigurableCase[string]{{ - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 25, - Line: 2, - Column: 25, - }, - Value: "asdf", - }, - }}, + Foo: newConfigurableWithPropertyName( + "foo", + nil, + []ConfigurableCase[string]{{ + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 25, + Line: 2, + Column: 25, + }, + Value: "asdf", }, - }, - }, - Bar: Configurable[bool]{ - propertyName: "bar", - inner: &configurableInner[bool]{ - single: singleConfigurable[bool]{ - cases: []ConfigurableCase[bool]{{ - value: &parser.Bool{ - LiteralPos: scanner.Position{ - Offset: 54, - Line: 3, - Column: 23, - }, - Value: true, - Token: "true", - }, - }}, + }}, + false, + ), + Bar: newConfigurableWithPropertyName( + "bar", + nil, + []ConfigurableCase[bool]{{ + value: &parser.Bool{ + LiteralPos: scanner.Position{ + Offset: 54, + Line: 3, + Column: 23, + }, + Value: true, + Token: "true", }, - }, - }, + }}, + false, + ), }, }, },