Compare commits

...

4 commits

Author SHA1 Message Date
Cole Faust
0a002f5985
Fix postProcessors having value semantics when unset
Currently, if postProcessors was empty, it would be set to a new
pointer value. But that would only apply for the current Configurable,
so if you passed a Configurable by value to a function and then
that function added a postProcessor, the caller wouldn't see that.

Configurable properties in general have a confusing mix of pointer
and value semantics (thanks go...), this fixes one part of it but in
followup cls I'll try to make it consistent.

Bug: 362579941
Test: m nothing --no-skip-soong-tests
Change-Id: Ib33497768be3af45233cf1a682adffb28ee64bfd
2024-10-24 19:18:21 +02:00
Cole Faust
c472e38ec1
Add PostProcessors to configurable properties
Some module types currently evaluate configurable properties in load
hooks, modify the results, and pass them onto properties of other
modules. Evaluating configurable properties in load hooks is
problematic, it happens so early that we can't decide the configuration
beforehand.

Add a "post processors" mechanism to configurable properties where
the result of evaluating the property will be passed through a post
processing function before being returned from Get(). This essentially
allows you to modify the property without evaluating it.

Bug: 362579941
Test: m nothing --no-skip-soong-tests
Change-Id: Ibddb3f14b3433364ba474b964c701e8915d4dc85
2024-10-24 19:18:21 +02:00
Cole Faust
738bb54ded
Support variable bindings in selects
This allows us to recreate soong config value variables in selects.

This adds a new "any" pattern to selects, which is the same as "default"
except that it doesn't match undefined variables, and it's (currently)
the only pattern that can accept a binding.

The syntax looks like:
```
select(soong_config_variable("my_namespace", "my_variable"), {
    any @ my_binding: "foo" + my_binding,
    default: "other value",
})
```

Bug: 323382414
Test: m nothing --no-skip-soong-tests
Change-Id: I4feb4073172d8797dee5472f43f9c248a76c3f1f
2024-10-24 19:18:21 +02:00
Cole Faust
1e62c68bfe
Separate blueprint parsing and evaluating
Before this cl, blueprint expressions were evaluated as they were
parsed. We want to add a feature to select statements where we can
bind the value of soome value from soong into a blueprint variable,
that then can be used like a regular variable in the .bp file. This
means that select statements need to hold whole unevalated expression
trees, and have the ability to evaluate them later on when the value
of the bound variable is known.

This cl doesn't implement the new select syntax, but it does split
blueprint's parsing and evaluating into two separate stages. We also
store expressions in selects and evaluate them when the select is
resolved.

I didn't do extensive performance evaluation, but a simple comparison
of the time of `touch Android.bp && m nothing` before/after this cl
showed a 1 second speedup. (That was probably just noise)

Bug: 323382414
Test: m nothing --no-skip-soong-tests
Change-Id: I12f373719991afeb4aec76517153f32229d97ff2
2024-10-24 19:18:21 +02:00
18 changed files with 1697 additions and 1380 deletions

View file

@ -133,6 +133,7 @@ bootstrap_go_package {
], ],
testSrcs: [ testSrcs: [
"proptools/clone_test.go", "proptools/clone_test.go",
"proptools/configurable_test.go",
"proptools/escape_test.go", "proptools/escape_test.go",
"proptools/extend_test.go", "proptools/extend_test.go",
"proptools/filter_test.go", "proptools/filter_test.go",

View file

@ -66,7 +66,7 @@ func processReader(filename string, in io.Reader, out io.Writer) error {
r := bytes.NewBuffer(src) r := bytes.NewBuffer(src)
file, errs := parser.Parse(filename, r, parser.NewScope(nil)) file, errs := parser.Parse(filename, r)
if len(errs) > 0 { if len(errs) > 0 {
for _, err := range errs { for _, err := range errs {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)

View file

@ -85,7 +85,7 @@ func processFile(filename string, in io.Reader, out io.Writer) error {
return err return err
} }
r := bytes.NewBuffer(src) r := bytes.NewBuffer(src)
file, errs := parser.Parse(filename, r, parser.NewScope(nil)) file, errs := parser.Parse(filename, r)
if len(errs) > 0 { if len(errs) > 0 {
for _, err := range errs { for _, err := range errs {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
@ -131,11 +131,13 @@ func findModules(file *parser.File) (modified bool, errs []error) {
for _, def := range file.Defs { for _, def := range file.Defs {
if module, ok := def.(*parser.Module); ok { if module, ok := def.(*parser.Module); ok {
for _, prop := range module.Properties { for _, prop := range module.Properties {
if prop.Name == "name" && prop.Value.Type() == parser.StringType && targetedModule(prop.Value.Eval().(*parser.String).Value) { if prop.Name == "name" {
for _, p := range targetedProperties.properties { if stringValue, ok := prop.Value.(*parser.String); ok && targetedModule(stringValue.Value) {
m, newErrs := processModuleProperty(module, prop.Name, file, p) for _, p := range targetedProperties.properties {
errs = append(errs, newErrs...) m, newErrs := processModuleProperty(module, prop.Name, file, p)
modified = modified || m errs = append(errs, newErrs...)
modified = modified || m
}
} }
} }
} }
@ -194,7 +196,7 @@ func getOrCreateRecursiveProperty(module *parser.Module, name string, prefixes [
m := &module.Map m := &module.Map
for i, prefix := range prefixes { for i, prefix := range prefixes {
if prop, found := m.GetProperty(prefix); found { if prop, found := m.GetProperty(prefix); found {
if mm, ok := prop.Value.Eval().(*parser.Map); ok { if mm, ok := prop.Value.(*parser.Map); ok {
m = mm m = mm
} else { } else {
// We've found a property in the AST and such property is not of type // We've found a property in the AST and such property is not of type
@ -236,9 +238,9 @@ func processParameter(value parser.Expression, paramName, moduleName string,
} }
if (*replaceProperty).size() != 0 { if (*replaceProperty).size() != 0 {
if list, ok := value.Eval().(*parser.List); ok { if list, ok := value.(*parser.List); ok {
return parser.ReplaceStringsInList(list, (*replaceProperty).oldNameToNewName), nil return parser.ReplaceStringsInList(list, (*replaceProperty).oldNameToNewName), nil
} else if str, ok := value.Eval().(*parser.String); ok { } else if str, ok := value.(*parser.String); ok {
oldVal := str.Value oldVal := str.Value
replacementValue := (*replaceProperty).oldNameToNewName[oldVal] replacementValue := (*replaceProperty).oldNameToNewName[oldVal]
if replacementValue != "" { if replacementValue != "" {

View file

@ -1221,9 +1221,9 @@ func (c *Context) parseOne(rootDir, filename string, reader io.Reader,
return nil, nil, []error{err} return nil, nil, []error{err}
} }
scope.Remove("subdirs") scope.DontInherit("subdirs")
scope.Remove("optional_subdirs") scope.DontInherit("optional_subdirs")
scope.Remove("build") scope.DontInherit("build")
file, errs = parser.ParseAndEval(filename, reader, scope) file, errs = parser.ParseAndEval(filename, reader, scope)
if len(errs) > 0 { if len(errs) > 0 {
for i, err := range errs { for i, err := range errs {
@ -1357,10 +1357,10 @@ func (c *Context) findSubdirBlueprints(dir string, subdirs []string, subdirsPos
} }
func getLocalStringListFromScope(scope *parser.Scope, v string) ([]string, scanner.Position, error) { func getLocalStringListFromScope(scope *parser.Scope, v string) ([]string, scanner.Position, error) {
if assignment, local := scope.Get(v); assignment == nil || !local { if assignment := scope.GetLocal(v); assignment == nil {
return nil, scanner.Position{}, nil return nil, scanner.Position{}, nil
} else { } else {
switch value := assignment.Value.Eval().(type) { switch value := assignment.Value.(type) {
case *parser.List: case *parser.List:
ret := make([]string, 0, len(value.Values)) ret := make([]string, 0, len(value.Values))
@ -1386,24 +1386,6 @@ func getLocalStringListFromScope(scope *parser.Scope, v string) ([]string, scann
} }
} }
func getStringFromScope(scope *parser.Scope, v string) (string, scanner.Position, error) {
if assignment, _ := scope.Get(v); assignment == nil {
return "", scanner.Position{}, nil
} else {
switch value := assignment.Value.Eval().(type) {
case *parser.String:
return value.Value, assignment.EqualsPos, nil
case *parser.Bool, *parser.List:
return "", scanner.Position{}, &BlueprintError{
Err: fmt.Errorf("%q must be a string", v),
Pos: assignment.EqualsPos,
}
default:
panic(fmt.Errorf("unknown value type: %d", assignment.Value.Type()))
}
}
}
// Clones a build logic module by calling the factory method for its module type, and then cloning // Clones a build logic module by calling the factory method for its module type, and then cloning
// property values. Any values stored in the module object that are not stored in properties // property values. Any values stored in the module object that are not stored in properties
// structs will be lost. // structs will be lost.

View file

@ -1392,8 +1392,7 @@ func runAndRemoveLoadHooks(ctx *Context, config interface{}, module *moduleInfo,
// //
// The filename is only used for reporting errors. // The filename is only used for reporting errors.
func CheckBlueprintSyntax(moduleFactories map[string]ModuleFactory, filename string, contents string) []error { func CheckBlueprintSyntax(moduleFactories map[string]ModuleFactory, filename string, contents string) []error {
scope := parser.NewScope(nil) file, errs := parser.Parse(filename, strings.NewReader(contents))
file, errs := parser.Parse(filename, strings.NewReader(contents), scope)
if len(errs) != 0 { if len(errs) != 0 {
return errs return errs
} }

View file

@ -16,6 +16,7 @@ package parser
import ( import (
"fmt" "fmt"
"os"
"strings" "strings"
"text/scanner" "text/scanner"
) )
@ -40,14 +41,13 @@ type Assignment struct {
Name string Name string
NamePos scanner.Position NamePos scanner.Position
Value Expression Value Expression
OrigValue Expression
EqualsPos scanner.Position EqualsPos scanner.Position
Assigner string Assigner string
Referenced bool Referenced bool
} }
func (a *Assignment) String() string { func (a *Assignment) String() string {
return fmt.Sprintf("%s@%s %s %s (%s) %t", a.Name, a.EqualsPos, a.Assigner, a.Value, a.OrigValue, a.Referenced) return fmt.Sprintf("%s@%s %s %s %t", a.Name, a.EqualsPos, a.Assigner, a.Value, a.Referenced)
} }
func (a *Assignment) Pos() scanner.Position { return a.NamePos } func (a *Assignment) Pos() scanner.Position { return a.NamePos }
@ -131,6 +131,10 @@ func (p *Property) String() string {
func (p *Property) Pos() scanner.Position { return p.NamePos } func (p *Property) Pos() scanner.Position { return p.NamePos }
func (p *Property) End() scanner.Position { return p.Value.End() } func (p *Property) End() scanner.Position { return p.Value.End() }
func (p *Property) MarkReferencedVariables(scope *Scope) {
p.Value.MarkReferencedVariables(scope)
}
// An Expression is a Value in a Property or Assignment. It can be a literal (String or Bool), a // An Expression is a Value in a Property or Assignment. It can be a literal (String or Bool), a
// Map, a List, an Operator that combines two expressions of the same type, or a Variable that // Map, a List, an Operator that combines two expressions of the same type, or a Variable that
// references and Assignment. // references and Assignment.
@ -139,11 +143,24 @@ type Expression interface {
// Copy returns a copy of the Expression that will not affect the original if mutated // Copy returns a copy of the Expression that will not affect the original if mutated
Copy() Expression Copy() Expression
String() string String() string
// Type returns the underlying Type enum of the Expression if it were to be evaluated // Type returns the underlying Type enum of the Expression if it were to be evaluated, if it's known.
// It's possible that the type isn't known, such as when a select statement with a late-bound variable
// is used. For that reason, Type() is mostly for use in error messages, not to make logic decisions
// off of.
Type() Type Type() Type
// Eval returns an expression that is fully evaluated to a simple type (List, Map, String, or // Eval returns an expression that is fully evaluated to a simple type (List, Map, String,
// Bool). It will return the same object for every call to Eval(). // Bool, or Select). It will return the origional expression if possible, or allocate a
Eval() Expression // new one if modifications were necessary.
Eval(scope *Scope) (Expression, error)
// PrintfInto will substitute any %s's in string literals in the AST with the provided
// value. It will modify the AST in-place. This is used to implement soong config value
// variables, but should be removed when those have switched to selects.
PrintfInto(value string) error
// MarkReferencedVariables marks the variables in the given scope referenced if there
// is a matching variable reference in this expression. This happens naturally during
// Eval as well, but for selects, we need to mark variables as referenced without
// actually evaluating the expression yet.
MarkReferencedVariables(scope *Scope)
} }
// ExpressionsAreSame tells whether the two values are the same Expression. // ExpressionsAreSame tells whether the two values are the same Expression.
@ -157,9 +174,6 @@ func ExpressionsAreSame(a Expression, b Expression) (equal bool, err error) {
// TODO(jeffrygaston) once positions are removed from Expression structs, // TODO(jeffrygaston) once positions are removed from Expression structs,
// remove this function and have callers use reflect.DeepEqual(a, b) // remove this function and have callers use reflect.DeepEqual(a, b)
func hackyExpressionsAreSame(a Expression, b Expression) (equal bool, err error) { func hackyExpressionsAreSame(a Expression, b Expression) (equal bool, err error) {
if a.Type() != b.Type() {
return false, nil
}
left, err := hackyFingerprint(a) left, err := hackyFingerprint(a)
if err != nil { if err != nil {
return false, nil return false, nil
@ -173,7 +187,7 @@ func hackyExpressionsAreSame(a Expression, b Expression) (equal bool, err error)
} }
func hackyFingerprint(expression Expression) (fingerprint []byte, err error) { func hackyFingerprint(expression Expression) (fingerprint []byte, err error) {
assignment := &Assignment{"a", noPos, expression, expression, noPos, "=", false} assignment := &Assignment{"a", noPos, expression, noPos, "=", false}
module := &File{} module := &File{}
module.Defs = append(module.Defs, assignment) module.Defs = append(module.Defs, assignment)
p := newPrinter(module) p := newPrinter(module)
@ -183,17 +197,19 @@ func hackyFingerprint(expression Expression) (fingerprint []byte, err error) {
type Type int type Type int
const ( const (
BoolType Type = iota + 1 UnknownType Type = iota
BoolType
StringType StringType
Int64Type Int64Type
ListType ListType
MapType MapType
NotEvaluatedType
UnsetType UnsetType
) )
func (t Type) String() string { func (t Type) String() string {
switch t { switch t {
case UnknownType:
return "unknown"
case BoolType: case BoolType:
return "bool" return "bool"
case StringType: case StringType:
@ -204,12 +220,10 @@ func (t Type) String() string {
return "list" return "list"
case MapType: case MapType:
return "map" return "map"
case NotEvaluatedType:
return "notevaluated"
case UnsetType: case UnsetType:
return "unset" return "unset"
default: default:
panic(fmt.Errorf("Unknown type %d", t)) panic(fmt.Sprintf("Unknown type %d", t))
} }
} }
@ -217,7 +231,6 @@ type Operator struct {
Args [2]Expression Args [2]Expression
Operator rune Operator rune
OperatorPos scanner.Position OperatorPos scanner.Position
Value Expression
} }
func (x *Operator) Copy() Expression { func (x *Operator) Copy() Expression {
@ -227,26 +240,142 @@ func (x *Operator) Copy() Expression {
return &ret return &ret
} }
func (x *Operator) Eval() Expression { func (x *Operator) Type() Type {
return x.Value.Eval() t1 := x.Args[0].Type()
t2 := x.Args[1].Type()
if t1 == UnknownType {
return t2
}
if t2 == UnknownType {
return t1
}
if t1 != t2 {
return UnknownType
}
return t1
} }
func (x *Operator) Type() Type { func (x *Operator) Eval(scope *Scope) (Expression, error) {
return x.Args[0].Type() return evaluateOperator(scope, x.Operator, x.Args[0], x.Args[1])
}
func evaluateOperator(scope *Scope, operator rune, left, right Expression) (Expression, error) {
if operator != '+' {
return nil, fmt.Errorf("unknown operator %c", operator)
}
l, err := left.Eval(scope)
if err != nil {
return nil, err
}
r, err := right.Eval(scope)
if err != nil {
return nil, err
}
if _, ok := l.(*Select); !ok {
if _, ok := r.(*Select); ok {
// Promote l to a select so we can add r to it
l = &Select{
Cases: []*SelectCase{{
Value: l,
}},
}
}
}
l = l.Copy()
switch v := l.(type) {
case *String:
if _, ok := r.(*String); !ok {
fmt.Fprintf(os.Stderr, "not ok")
}
v.Value += r.(*String).Value
case *Int64:
v.Value += r.(*Int64).Value
v.Token = ""
case *List:
v.Values = append(v.Values, r.(*List).Values...)
case *Map:
var err error
v.Properties, err = addMaps(scope, v.Properties, r.(*Map).Properties)
if err != nil {
return nil, err
}
case *Select:
v.Append = r
default:
return nil, fmt.Errorf("operator %c not supported on %v", operator, v)
}
return l, nil
}
func addMaps(scope *Scope, map1, map2 []*Property) ([]*Property, error) {
ret := make([]*Property, 0, len(map1))
inMap1 := make(map[string]*Property)
inMap2 := make(map[string]*Property)
inBoth := make(map[string]*Property)
for _, prop1 := range map1 {
inMap1[prop1.Name] = prop1
}
for _, prop2 := range map2 {
inMap2[prop2.Name] = prop2
if _, ok := inMap1[prop2.Name]; ok {
inBoth[prop2.Name] = prop2
}
}
for _, prop1 := range map1 {
if prop2, ok := inBoth[prop1.Name]; ok {
var err error
newProp := *prop1
newProp.Value, err = evaluateOperator(scope, '+', prop1.Value, prop2.Value)
if err != nil {
return nil, err
}
ret = append(ret, &newProp)
} else {
ret = append(ret, prop1)
}
}
for _, prop2 := range map2 {
if _, ok := inBoth[prop2.Name]; !ok {
ret = append(ret, prop2)
}
}
return ret, nil
}
func (x *Operator) PrintfInto(value string) error {
if err := x.Args[0].PrintfInto(value); err != nil {
return err
}
return x.Args[1].PrintfInto(value)
}
func (x *Operator) MarkReferencedVariables(scope *Scope) {
x.Args[0].MarkReferencedVariables(scope)
x.Args[1].MarkReferencedVariables(scope)
} }
func (x *Operator) Pos() scanner.Position { return x.Args[0].Pos() } func (x *Operator) Pos() scanner.Position { return x.Args[0].Pos() }
func (x *Operator) End() scanner.Position { return x.Args[1].End() } func (x *Operator) End() scanner.Position { return x.Args[1].End() }
func (x *Operator) String() string { func (x *Operator) String() string {
return fmt.Sprintf("(%s %c %s = %s)@%s", x.Args[0].String(), x.Operator, x.Args[1].String(), return fmt.Sprintf("(%s %c %s)@%s", x.Args[0].String(), x.Operator, x.Args[1].String(),
x.Value, x.OperatorPos) x.OperatorPos)
} }
type Variable struct { type Variable struct {
Name string Name string
NamePos scanner.Position NamePos scanner.Position
Value Expression Type_ Type
} }
func (x *Variable) Pos() scanner.Position { return x.NamePos } func (x *Variable) Pos() scanner.Position { return x.NamePos }
@ -257,15 +386,33 @@ func (x *Variable) Copy() Expression {
return &ret return &ret
} }
func (x *Variable) Eval() Expression { func (x *Variable) Eval(scope *Scope) (Expression, error) {
return x.Value.Eval() if assignment := scope.Get(x.Name); assignment != nil {
assignment.Referenced = true
return assignment.Value, nil
}
return nil, fmt.Errorf("undefined variable %s", x.Name)
}
func (x *Variable) PrintfInto(value string) error {
return nil
}
func (x *Variable) MarkReferencedVariables(scope *Scope) {
if assignment := scope.Get(x.Name); assignment != nil {
assignment.Referenced = true
}
} }
func (x *Variable) String() string { func (x *Variable) String() string {
return x.Name + " = " + x.Value.String() return x.Name
} }
func (x *Variable) Type() Type { return x.Value.Type() } func (x *Variable) Type() Type {
// Variables do not normally have a type associated with them, this is only
// filled out in the androidmk tool
return x.Type_
}
type Map struct { type Map struct {
LBracePos scanner.Position LBracePos scanner.Position
@ -285,8 +432,36 @@ func (x *Map) Copy() Expression {
return &ret return &ret
} }
func (x *Map) Eval() Expression { func (x *Map) Eval(scope *Scope) (Expression, error) {
return x newProps := make([]*Property, len(x.Properties))
for i, prop := range x.Properties {
newVal, err := prop.Value.Eval(scope)
if err != nil {
return nil, err
}
newProps[i] = &Property{
Name: prop.Name,
NamePos: prop.NamePos,
ColonPos: prop.ColonPos,
Value: newVal,
}
}
return &Map{
LBracePos: x.LBracePos,
RBracePos: x.RBracePos,
Properties: newProps,
}, nil
}
func (x *Map) PrintfInto(value string) error {
// We should never reach this because selects cannot hold maps
panic("printfinto() is unsupported on maps")
}
func (x *Map) MarkReferencedVariables(scope *Scope) {
for _, prop := range x.Properties {
prop.MarkReferencedVariables(scope)
}
} }
func (x *Map) String() string { func (x *Map) String() string {
@ -379,8 +554,35 @@ func (x *List) Copy() Expression {
return &ret return &ret
} }
func (x *List) Eval() Expression { func (x *List) Eval(scope *Scope) (Expression, error) {
return x newValues := make([]Expression, len(x.Values))
for i, val := range x.Values {
newVal, err := val.Eval(scope)
if err != nil {
return nil, err
}
newValues[i] = newVal
}
return &List{
LBracePos: x.LBracePos,
RBracePos: x.RBracePos,
Values: newValues,
}, nil
}
func (x *List) PrintfInto(value string) error {
for _, val := range x.Values {
if err := val.PrintfInto(value); err != nil {
return err
}
}
return nil
}
func (x *List) MarkReferencedVariables(scope *Scope) {
for _, val := range x.Values {
val.MarkReferencedVariables(scope)
}
} }
func (x *List) String() string { func (x *List) String() string {
@ -407,8 +609,29 @@ func (x *String) Copy() Expression {
return &ret return &ret
} }
func (x *String) Eval() Expression { func (x *String) Eval(scope *Scope) (Expression, error) {
return x return x, nil
}
func (x *String) PrintfInto(value string) error {
count := strings.Count(x.Value, "%")
if count == 0 {
return nil
}
if count > 1 {
return fmt.Errorf("list/value variable properties only support a single '%%'")
}
if !strings.Contains(x.Value, "%s") {
return fmt.Errorf("unsupported %% in value variable property")
}
x.Value = fmt.Sprintf(x.Value, value)
return nil
}
func (x *String) MarkReferencedVariables(scope *Scope) {
} }
func (x *String) String() string { func (x *String) String() string {
@ -433,8 +656,15 @@ func (x *Int64) Copy() Expression {
return &ret return &ret
} }
func (x *Int64) Eval() Expression { func (x *Int64) Eval(scope *Scope) (Expression, error) {
return x return x, nil
}
func (x *Int64) PrintfInto(value string) error {
return nil
}
func (x *Int64) MarkReferencedVariables(scope *Scope) {
} }
func (x *Int64) String() string { func (x *Int64) String() string {
@ -459,8 +689,15 @@ func (x *Bool) Copy() Expression {
return &ret return &ret
} }
func (x *Bool) Eval() Expression { func (x *Bool) Eval(scope *Scope) (Expression, error) {
return x return x, nil
}
func (x *Bool) PrintfInto(value string) error {
return nil
}
func (x *Bool) MarkReferencedVariables(scope *Scope) {
} }
func (x *Bool) String() string { func (x *Bool) String() string {
@ -542,29 +779,6 @@ func (c Comment) Text() string {
return string(buf) return string(buf)
} }
type NotEvaluated struct {
Position scanner.Position
}
func (n NotEvaluated) Copy() Expression {
return NotEvaluated{Position: n.Position}
}
func (n NotEvaluated) String() string {
return "Not Evaluated"
}
func (n NotEvaluated) Type() Type {
return NotEvaluatedType
}
func (n NotEvaluated) Eval() Expression {
return NotEvaluated{Position: n.Position}
}
func (n NotEvaluated) Pos() scanner.Position { return n.Position }
func (n NotEvaluated) End() scanner.Position { return n.Position }
func endPos(pos scanner.Position, n int) scanner.Position { func endPos(pos scanner.Position, n int) scanner.Position {
pos.Offset += n pos.Offset += n
pos.Column += n pos.Column += n
@ -609,13 +823,13 @@ func (c *ConfigurableCondition) String() string {
} }
type Select struct { type Select struct {
KeywordPos scanner.Position // the keyword "select" Scope *Scope // scope used to evaluate the body of the select later on
Conditions []ConfigurableCondition KeywordPos scanner.Position // the keyword "select"
LBracePos scanner.Position Conditions []ConfigurableCondition
RBracePos scanner.Position LBracePos scanner.Position
Cases []*SelectCase // the case statements RBracePos scanner.Position
Append Expression Cases []*SelectCase // the case statements
ExpressionType Type Append Expression
} }
func (s *Select) Pos() scanner.Position { return s.KeywordPos } func (s *Select) Pos() scanner.Position { return s.KeywordPos }
@ -633,8 +847,24 @@ func (s *Select) Copy() Expression {
return &ret return &ret
} }
func (s *Select) Eval() Expression { func (s *Select) Eval(scope *Scope) (Expression, error) {
return s s.Scope = scope
s.MarkReferencedVariables(scope)
return s, nil
}
func (x *Select) PrintfInto(value string) error {
// PrintfInto will be handled at the Configurable object level
panic("Cannot call PrintfInto on a select expression")
}
func (x *Select) MarkReferencedVariables(scope *Scope) {
for _, c := range x.Cases {
c.MarkReferencedVariables(scope)
}
if x.Append != nil {
x.Append.MarkReferencedVariables(scope)
}
} }
func (s *Select) String() string { func (s *Select) String() string {
@ -642,18 +872,35 @@ func (s *Select) String() string {
} }
func (s *Select) Type() Type { func (s *Select) Type() Type {
if s.ExpressionType == UnsetType && s.Append != nil { if len(s.Cases) == 0 {
return s.Append.Type() return UnsetType
} }
return s.ExpressionType return UnknownType
}
type SelectPattern struct {
Value Expression
Binding Variable
}
func (c *SelectPattern) Pos() scanner.Position { return c.Value.Pos() }
func (c *SelectPattern) End() scanner.Position {
if c.Binding.NamePos.IsValid() {
return c.Binding.End()
}
return c.Value.End()
} }
type SelectCase struct { type SelectCase struct {
Patterns []Expression Patterns []SelectPattern
ColonPos scanner.Position ColonPos scanner.Position
Value Expression Value Expression
} }
func (x *SelectCase) MarkReferencedVariables(scope *Scope) {
x.Value.MarkReferencedVariables(scope)
}
func (c *SelectCase) Copy() *SelectCase { func (c *SelectCase) Copy() *SelectCase {
ret := *c ret := *c
ret.Value = c.Value.Copy() ret.Value = c.Value.Copy()
@ -681,21 +928,28 @@ type UnsetProperty struct {
Position scanner.Position Position scanner.Position
} }
func (n UnsetProperty) Copy() Expression { func (n *UnsetProperty) Copy() Expression {
return UnsetProperty{Position: n.Position} return &UnsetProperty{Position: n.Position}
} }
func (n UnsetProperty) String() string { func (n *UnsetProperty) String() string {
return "unset" return "unset"
} }
func (n UnsetProperty) Type() Type { func (n *UnsetProperty) Type() Type {
return UnsetType return UnsetType
} }
func (n UnsetProperty) Eval() Expression { func (n *UnsetProperty) Eval(scope *Scope) (Expression, error) {
return UnsetProperty{Position: n.Position} return n, nil
} }
func (n UnsetProperty) Pos() scanner.Position { return n.Position } func (x *UnsetProperty) PrintfInto(value string) error {
func (n UnsetProperty) End() scanner.Position { return n.Position } return nil
}
func (x *UnsetProperty) MarkReferencedVariables(scope *Scope) {
}
func (n *UnsetProperty) Pos() scanner.Position { return n.Position }
func (n *UnsetProperty) End() scanner.Position { return n.Position }

View file

@ -23,13 +23,11 @@ import (
func AddStringToList(list *List, s string) (modified bool) { func AddStringToList(list *List, s string) (modified bool) {
for _, v := range list.Values { for _, v := range list.Values {
if v.Type() != StringType {
panic(fmt.Errorf("expected string in list, got %s", v.Type()))
}
if sv, ok := v.(*String); ok && sv.Value == s { if sv, ok := v.(*String); ok && sv.Value == s {
// string already exists // string already exists
return false return false
} else if !ok {
panic(fmt.Errorf("expected string in list, got %s", v.Type()))
} }
} }
@ -43,13 +41,11 @@ func AddStringToList(list *List, s string) (modified bool) {
func RemoveStringFromList(list *List, s string) (modified bool) { func RemoveStringFromList(list *List, s string) (modified bool) {
for i, v := range list.Values { for i, v := range list.Values {
if v.Type() != StringType {
panic(fmt.Errorf("expected string in list, got %s", v.Type()))
}
if sv, ok := v.(*String); ok && sv.Value == s { if sv, ok := v.(*String); ok && sv.Value == s {
list.Values = append(list.Values[:i], list.Values[i+1:]...) list.Values = append(list.Values[:i], list.Values[i+1:]...)
return true return true
} else if !ok {
panic(fmt.Errorf("expected string in list, got %s", v.Type()))
} }
} }
@ -59,9 +55,6 @@ func RemoveStringFromList(list *List, s string) (modified bool) {
func ReplaceStringsInList(list *List, replacements map[string]string) (replaced bool) { func ReplaceStringsInList(list *List, replacements map[string]string) (replaced bool) {
modified := false modified := false
for i, v := range list.Values { for i, v := range list.Values {
if v.Type() != StringType {
panic(fmt.Errorf("expected string in list, got %s", v.Type()))
}
if sv, ok := v.(*String); ok && replacements[sv.Value] != "" { if sv, ok := v.(*String); ok && replacements[sv.Value] != "" {
pos := list.Values[i].Pos() pos := list.Values[i].Pos()
list.Values[i] = &String{ list.Values[i] = &String{
@ -69,6 +62,8 @@ func ReplaceStringsInList(list *List, replacements map[string]string) (replaced
Value: replacements[sv.Value], Value: replacements[sv.Value],
} }
modified = true modified = true
} else if !ok {
panic(fmt.Errorf("expected string in list, got %s", v.Type()))
} }
} }
return modified return modified

View file

@ -29,6 +29,7 @@ var errTooManyErrors = errors.New("too many errors")
const maxErrors = 1 const maxErrors = 1
const default_select_branch_name = "__soong_conditions_default__" const default_select_branch_name = "__soong_conditions_default__"
const any_select_branch_name = "__soong_conditions_any__"
type ParseError struct { type ParseError struct {
Err error Err error
@ -45,22 +46,6 @@ type File struct {
Comments []*CommentGroup Comments []*CommentGroup
} }
func (f *File) Pos() scanner.Position {
return scanner.Position{
Filename: f.Name,
Line: 1,
Column: 1,
Offset: 0,
}
}
func (f *File) End() scanner.Position {
if len(f.Defs) > 0 {
return f.Defs[len(f.Defs)-1].End()
}
return noPos
}
func parse(p *parser) (file *File, errs []error) { func parse(p *parser) (file *File, errs []error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -87,22 +72,54 @@ func parse(p *parser) (file *File, errs []error) {
} }
func ParseAndEval(filename string, r io.Reader, scope *Scope) (file *File, errs []error) { func ParseAndEval(filename string, r io.Reader, scope *Scope) (file *File, errs []error) {
p := newParser(r, scope) file, errs = Parse(filename, r)
p.eval = true if len(errs) > 0 {
p.scanner.Filename = filename return nil, errs
}
return parse(p) // evaluate all module properties
var newDefs []Definition
for _, def := range file.Defs {
switch d := def.(type) {
case *Module:
for _, prop := range d.Map.Properties {
newval, err := prop.Value.Eval(scope)
if err != nil {
return nil, []error{err}
}
switch newval.(type) {
case *String, *Bool, *Int64, *Select, *Map, *List:
// ok
default:
panic(fmt.Sprintf("Evaled but got %#v\n", newval))
}
prop.Value = newval
}
newDefs = append(newDefs, d)
case *Assignment:
if err := scope.HandleAssignment(d); err != nil {
return nil, []error{err}
}
}
}
// This is not strictly necessary, but removing the assignments from
// the result makes it clearer that this is an evaluated file.
// We could also consider adding a "EvaluatedFile" type to return.
file.Defs = newDefs
return file, nil
} }
func Parse(filename string, r io.Reader, scope *Scope) (file *File, errs []error) { func Parse(filename string, r io.Reader) (file *File, errs []error) {
p := newParser(r, scope) p := newParser(r)
p.scanner.Filename = filename p.scanner.Filename = filename
return parse(p) return parse(p)
} }
func ParseExpression(r io.Reader) (value Expression, errs []error) { func ParseExpression(r io.Reader) (value Expression, errs []error) {
p := newParser(r, NewScope(nil)) p := newParser(r)
p.next() p.next()
value = p.parseExpression() value = p.parseExpression()
p.accept(scanner.EOF) p.accept(scanner.EOF)
@ -114,14 +131,11 @@ type parser struct {
scanner scanner.Scanner scanner scanner.Scanner
tok rune tok rune
errors []error errors []error
scope *Scope
comments []*CommentGroup comments []*CommentGroup
eval bool
} }
func newParser(r io.Reader, scope *Scope) *parser { func newParser(r io.Reader) *parser {
p := &parser{} p := &parser{}
p.scope = scope
p.scanner.Init(r) p.scanner.Init(r)
p.scanner.Error = func(sc *scanner.Scanner, msg string) { p.scanner.Error = func(sc *scanner.Scanner, msg string) {
p.errorf(msg) p.errorf(msg)
@ -234,34 +248,9 @@ func (p *parser) parseAssignment(name string, namePos scanner.Position,
assignment.Name = name assignment.Name = name
assignment.NamePos = namePos assignment.NamePos = namePos
assignment.Value = value assignment.Value = value
assignment.OrigValue = value
assignment.EqualsPos = pos assignment.EqualsPos = pos
assignment.Assigner = assigner assignment.Assigner = assigner
if p.scope != nil {
if assigner == "+=" {
if old, local := p.scope.Get(assignment.Name); old == nil {
p.errorf("modified non-existent variable %q with +=", assignment.Name)
} else if !local {
p.errorf("modified non-local variable %q with +=", assignment.Name)
} else if old.Referenced {
p.errorf("modified variable %q with += after referencing", assignment.Name)
} else {
val, err := p.evaluateOperator(old.Value, assignment.Value, '+', assignment.EqualsPos)
if err != nil {
p.error(err)
} else {
old.Value = val
}
}
} else {
err := p.scope.Add(assignment)
if err != nil {
p.error(err)
}
}
}
return return
} }
@ -297,13 +286,7 @@ func (p *parser) parseModule(typ string, typPos scanner.Position) *Module {
func (p *parser) parsePropertyList(isModule, compat bool) (properties []*Property) { func (p *parser) parsePropertyList(isModule, compat bool) (properties []*Property) {
for p.tok == scanner.Ident { for p.tok == scanner.Ident {
property := p.parseProperty(isModule, compat) properties = append(properties, p.parseProperty(isModule, compat))
// If a property is set to an empty select or a select where all branches are "unset",
// skip emitting the property entirely.
if property.Value.Type() != UnsetType {
properties = append(properties, property)
}
if p.tok != ',' { if p.tok != ',' {
// There was no comma, so the list is done. // There was no comma, so the list is done.
@ -363,115 +346,6 @@ func (p *parser) parseExpression() (value Expression) {
} }
} }
func (p *parser) evaluateOperator(value1, value2 Expression, operator rune,
pos scanner.Position) (Expression, error) {
if value1.Type() == UnsetType {
return value2, nil
}
if value2.Type() == UnsetType {
return value1, nil
}
value := value1
if p.eval {
e1 := value1.Eval()
e2 := value2.Eval()
if e1.Type() != e2.Type() {
return nil, fmt.Errorf("mismatched type in operator %c: %s != %s", operator,
e1.Type(), e2.Type())
}
if _, ok := e1.(*Select); !ok {
if _, ok := e2.(*Select); ok {
// Promote e1 to a select so we can add e2 to it
e1 = &Select{
Cases: []*SelectCase{{
Value: e1,
}},
ExpressionType: e1.Type(),
}
}
}
value = e1.Copy()
switch operator {
case '+':
switch v := value.(type) {
case *String:
v.Value += e2.(*String).Value
case *Int64:
v.Value += e2.(*Int64).Value
v.Token = ""
case *List:
v.Values = append(v.Values, e2.(*List).Values...)
case *Map:
var err error
v.Properties, err = p.addMaps(v.Properties, e2.(*Map).Properties, pos)
if err != nil {
return nil, err
}
case *Select:
v.Append = e2
default:
return nil, fmt.Errorf("operator %c not supported on type %s", operator, v.Type())
}
default:
panic("unknown operator " + string(operator))
}
}
return &Operator{
Args: [2]Expression{value1, value2},
Operator: operator,
OperatorPos: pos,
Value: value,
}, nil
}
func (p *parser) addMaps(map1, map2 []*Property, pos scanner.Position) ([]*Property, error) {
ret := make([]*Property, 0, len(map1))
inMap1 := make(map[string]*Property)
inMap2 := make(map[string]*Property)
inBoth := make(map[string]*Property)
for _, prop1 := range map1 {
inMap1[prop1.Name] = prop1
}
for _, prop2 := range map2 {
inMap2[prop2.Name] = prop2
if _, ok := inMap1[prop2.Name]; ok {
inBoth[prop2.Name] = prop2
}
}
for _, prop1 := range map1 {
if prop2, ok := inBoth[prop1.Name]; ok {
var err error
newProp := *prop1
newProp.Value, err = p.evaluateOperator(prop1.Value, prop2.Value, '+', pos)
if err != nil {
return nil, err
}
ret = append(ret, &newProp)
} else {
ret = append(ret, prop1)
}
}
for _, prop2 := range map2 {
if _, ok := inBoth[prop2.Name]; !ok {
ret = append(ret, prop2)
}
}
return ret, nil
}
func (p *parser) parseOperator(value1 Expression) Expression { func (p *parser) parseOperator(value1 Expression) Expression {
operator := p.tok operator := p.tok
pos := p.scanner.Position pos := p.scanner.Position
@ -479,14 +353,11 @@ func (p *parser) parseOperator(value1 Expression) Expression {
value2 := p.parseExpression() value2 := p.parseExpression()
value, err := p.evaluateOperator(value1, value2, operator, pos) return &Operator{
if err != nil { Args: [2]Expression{value1, value2},
p.error(err) Operator: operator,
return nil OperatorPos: pos,
} }
return value
} }
func (p *parser) parseValue() (value Expression) { func (p *parser) parseValue() (value Expression) {
@ -535,22 +406,9 @@ func (p *parser) parseVariable() Expression {
var value Expression var value Expression
text := p.scanner.TokenText() text := p.scanner.TokenText()
if p.eval {
if assignment, local := p.scope.Get(text); assignment == nil {
p.errorf("variable %q is not set", text)
} else {
if local {
assignment.Referenced = true
}
value = assignment.Value
}
} else {
value = &NotEvaluated{}
}
value = &Variable{ value = &Variable{
Name: text, Name: text,
NamePos: p.scanner.Position, NamePos: p.scanner.Position,
Value: value,
} }
p.accept(scanner.Ident) p.accept(scanner.Ident)
@ -645,44 +503,72 @@ func (p *parser) parseSelect() Expression {
return nil return nil
} }
parseOnePattern := func() Expression { maybeParseBinding := func() (Variable, bool) {
if p.scanner.TokenText() != "@" {
return Variable{}, false
}
p.next()
value := Variable{
Name: p.scanner.TokenText(),
NamePos: p.scanner.Position,
}
p.accept(scanner.Ident)
return value, true
}
parseOnePattern := func() SelectPattern {
var result SelectPattern
switch p.tok { switch p.tok {
case scanner.Ident: case scanner.Ident:
switch p.scanner.TokenText() { switch p.scanner.TokenText() {
case "default": case "any":
result.Value = &String{
LiteralPos: p.scanner.Position,
Value: any_select_branch_name,
}
p.next() p.next()
return &String{ if binding, exists := maybeParseBinding(); exists {
result.Binding = binding
}
return result
case "default":
result.Value = &String{
LiteralPos: p.scanner.Position, LiteralPos: p.scanner.Position,
Value: default_select_branch_name, Value: default_select_branch_name,
} }
case "true":
p.next() p.next()
return &Bool{ return result
case "true":
result.Value = &Bool{
LiteralPos: p.scanner.Position, LiteralPos: p.scanner.Position,
Value: true, Value: true,
} }
case "false":
p.next() p.next()
return &Bool{ return result
case "false":
result.Value = &Bool{
LiteralPos: p.scanner.Position, LiteralPos: p.scanner.Position,
Value: false, Value: false,
} }
p.next()
return result
default: default:
p.errorf("Expted a string, true, false, or default, got %s", p.scanner.TokenText()) p.errorf("Expected a string, true, false, or default, got %s", p.scanner.TokenText())
} }
case scanner.String: case scanner.String:
if s := p.parseStringValue(); s != nil { if s := p.parseStringValue(); s != nil {
if strings.HasPrefix(s.Value, "__soong") { if strings.HasPrefix(s.Value, "__soong") {
p.errorf("select branch conditions starting with __soong are reserved for internal use") p.errorf("select branch patterns starting with __soong are reserved for internal use")
return nil return result
} }
return s result.Value = s
return result
} }
fallthrough fallthrough
default: default:
p.errorf("Expted a string, true, false, or default, got %s", p.scanner.TokenText()) p.errorf("Expected a string, true, false, or default, got %s", p.scanner.TokenText())
} }
return nil return result
} }
hasNonUnsetValue := false hasNonUnsetValue := false
@ -694,11 +580,7 @@ func (p *parser) parseSelect() Expression {
return nil return nil
} }
for i := 0; i < len(conditions); i++ { for i := 0; i < len(conditions); i++ {
if p := parseOnePattern(); p != nil { c.Patterns = append(c.Patterns, parseOnePattern())
c.Patterns = append(c.Patterns, p)
} else {
return nil
}
if i < len(conditions)-1 { if i < len(conditions)-1 {
if !p.accept(',') { if !p.accept(',') {
return nil return nil
@ -712,18 +594,14 @@ func (p *parser) parseSelect() Expression {
return nil return nil
} }
} else { } else {
if p := parseOnePattern(); p != nil { c.Patterns = append(c.Patterns, parseOnePattern())
c.Patterns = append(c.Patterns, p)
} else {
return nil
}
} }
c.ColonPos = p.scanner.Position c.ColonPos = p.scanner.Position
if !p.accept(':') { if !p.accept(':') {
return nil return nil
} }
if p.tok == scanner.Ident && p.scanner.TokenText() == "unset" { if p.tok == scanner.Ident && p.scanner.TokenText() == "unset" {
c.Value = UnsetProperty{Position: p.scanner.Position} c.Value = &UnsetProperty{Position: p.scanner.Position}
p.accept(scanner.Ident) p.accept(scanner.Ident)
} else { } else {
hasNonUnsetValue = true hasNonUnsetValue = true
@ -742,16 +620,17 @@ func (p *parser) parseSelect() Expression {
return nil return nil
} }
patternsEqual := func(a, b Expression) bool { patternsEqual := func(a, b SelectPattern) bool {
switch a2 := a.(type) { // We can ignore the bindings, they don't affect which pattern is matched
switch a2 := a.Value.(type) {
case *String: case *String:
if b2, ok := b.(*String); ok { if b2, ok := b.Value.(*String); ok {
return a2.Value == b2.Value return a2.Value == b2.Value
} else { } else {
return false return false
} }
case *Bool: case *Bool:
if b2, ok := b.(*Bool); ok { if b2, ok := b.Value.(*Bool); ok {
return a2.Value == b2.Value return a2.Value == b2.Value
} else { } else {
return false return false
@ -762,7 +641,7 @@ func (p *parser) parseSelect() Expression {
} }
} }
patternListsEqual := func(a, b []Expression) bool { patternListsEqual := func(a, b []SelectPattern) bool {
if len(a) != len(b) { if len(a) != len(b) {
return false return false
} }
@ -775,18 +654,29 @@ func (p *parser) parseSelect() Expression {
} }
for i, c := range result.Cases { for i, c := range result.Cases {
// Check for duplicates // Check for duplicate patterns across different branches
for _, d := range result.Cases[i+1:] { for _, d := range result.Cases[i+1:] {
if patternListsEqual(c.Patterns, d.Patterns) { if patternListsEqual(c.Patterns, d.Patterns) {
p.errorf("Found duplicate select patterns: %v", c.Patterns) p.errorf("Found duplicate select patterns: %v", c.Patterns)
return nil return nil
} }
} }
// check for duplicate bindings within this branch
for i := range c.Patterns {
if c.Patterns[i].Binding.Name != "" {
for j := i + 1; j < len(c.Patterns); j++ {
if c.Patterns[i].Binding.Name == c.Patterns[j].Binding.Name {
p.errorf("Found duplicate select pattern binding: %s", c.Patterns[i].Binding.Name)
return nil
}
}
}
}
// Check that the only all-default cases is the last one // Check that the only all-default cases is the last one
if i < len(result.Cases)-1 { if i < len(result.Cases)-1 {
isAllDefault := true isAllDefault := true
for _, x := range c.Patterns { for _, x := range c.Patterns {
if x2, ok := x.(*String); !ok || x2.Value != default_select_branch_name { if x2, ok := x.Value.(*String); !ok || x2.Value != default_select_branch_name {
isAllDefault = false isAllDefault = false
break break
} }
@ -798,21 +688,6 @@ func (p *parser) parseSelect() Expression {
} }
} }
ty := UnsetType
for _, c := range result.Cases {
otherTy := c.Value.Type()
// Any other type can override UnsetType
if ty == UnsetType {
ty = otherTy
}
if otherTy != UnsetType && otherTy != ty {
p.errorf("Found select statement with differing types %q and %q in its cases", ty.String(), otherTy.String())
return nil
}
}
result.ExpressionType = ty
result.RBracePos = p.scanner.Position result.RBracePos = p.scanner.Position
if !p.accept('}') { if !p.accept('}') {
return nil return nil
@ -913,79 +788,107 @@ func (p *parser) parseMapValue() *Map {
} }
type Scope struct { type Scope struct {
vars map[string]*Assignment vars map[string]*Assignment
inheritedVars map[string]*Assignment preventInheriting map[string]bool
parentScope *Scope
} }
func NewScope(s *Scope) *Scope { func NewScope(s *Scope) *Scope {
newScope := &Scope{ return &Scope{
vars: make(map[string]*Assignment), vars: make(map[string]*Assignment),
inheritedVars: make(map[string]*Assignment), preventInheriting: make(map[string]bool),
parentScope: s,
} }
if s != nil {
for k, v := range s.vars {
newScope.inheritedVars[k] = v
}
for k, v := range s.inheritedVars {
newScope.inheritedVars[k] = v
}
}
return newScope
} }
func (s *Scope) Add(assignment *Assignment) error { func (s *Scope) HandleAssignment(assignment *Assignment) error {
if old, ok := s.vars[assignment.Name]; ok { switch assignment.Assigner {
return fmt.Errorf("variable already set, previous assignment: %s", old) case "+=":
if !s.preventInheriting[assignment.Name] && s.parentScope.Get(assignment.Name) != nil {
return fmt.Errorf("modified non-local variable %q with +=", assignment.Name)
}
if old, ok := s.vars[assignment.Name]; !ok {
return fmt.Errorf("modified non-existent variable %q with +=", assignment.Name)
} else if old.Referenced {
return fmt.Errorf("modified variable %q with += after referencing", assignment.Name)
} else {
newValue, err := evaluateOperator(s, '+', old.Value, assignment.Value)
if err != nil {
return err
}
old.Value = newValue
}
case "=":
if old, ok := s.vars[assignment.Name]; ok {
return fmt.Errorf("variable already set, previous assignment: %s", old)
}
if old := s.parentScope.Get(assignment.Name); old != nil && !s.preventInheriting[assignment.Name] {
return fmt.Errorf("variable already set in inherited scope, previous assignment: %s", old)
}
if newValue, err := assignment.Value.Eval(s); err != nil {
return err
} else {
assignment.Value = newValue
}
s.vars[assignment.Name] = assignment
default:
return fmt.Errorf("Unknown assigner '%s'", assignment.Assigner)
} }
if old, ok := s.inheritedVars[assignment.Name]; ok {
return fmt.Errorf("variable already set in inherited scope, previous assignment: %s", old)
}
s.vars[assignment.Name] = assignment
return nil return nil
} }
func (s *Scope) Remove(name string) { func (s *Scope) Get(name string) *Assignment {
delete(s.vars, name) if s == nil {
delete(s.inheritedVars, name) return nil
}
if a, ok := s.vars[name]; ok {
return a
}
if s.preventInheriting[name] {
return nil
}
return s.parentScope.Get(name)
} }
func (s *Scope) Get(name string) (*Assignment, bool) { func (s *Scope) GetLocal(name string) *Assignment {
if s == nil {
return nil
}
if a, ok := s.vars[name]; ok { if a, ok := s.vars[name]; ok {
return a, true return a
} }
return nil
}
if a, ok := s.inheritedVars[name]; ok { // DontInherit prevents this scope from inheriting the given variable from its
return a, false // parent scope.
} func (s *Scope) DontInherit(name string) {
s.preventInheriting[name] = true
return nil, false
} }
func (s *Scope) String() string { func (s *Scope) String() string {
vars := []string{} var sb strings.Builder
s.stringInner(&sb)
return sb.String()
}
for k := range s.vars { func (s *Scope) stringInner(sb *strings.Builder) {
vars = append(vars, k) if s == nil {
return
} }
for k := range s.inheritedVars { vars := make([]string, 0, len(s.vars))
for k := range s.vars {
vars = append(vars, k) vars = append(vars, k)
} }
sort.Strings(vars) sort.Strings(vars)
ret := []string{}
for _, v := range vars { for _, v := range vars {
if assignment, ok := s.vars[v]; ok { sb.WriteString(s.vars[v].String())
ret = append(ret, assignment.String()) sb.WriteRune('\n')
} else {
ret = append(ret, s.inheritedVars[v].String())
}
} }
return strings.Join(ret, "\n") s.parentScope.stringInner(sb)
} }

View file

@ -565,12 +565,7 @@ var validParseTestCases = []struct {
LiteralPos: mkpos(9, 2, 9), LiteralPos: mkpos(9, 2, 9),
Value: "stuff", Value: "stuff",
}, },
OrigValue: &String{ Assigner: "=",
LiteralPos: mkpos(9, 2, 9),
Value: "stuff",
},
Assigner: "=",
Referenced: true,
}, },
&Assignment{ &Assignment{
Name: "bar", Name: "bar",
@ -579,21 +574,8 @@ var validParseTestCases = []struct {
Value: &Variable{ Value: &Variable{
Name: "foo", Name: "foo",
NamePos: mkpos(25, 3, 9), NamePos: mkpos(25, 3, 9),
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuff",
},
}, },
OrigValue: &Variable{ Assigner: "=",
Name: "foo",
NamePos: mkpos(25, 3, 9),
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuff",
},
},
Assigner: "=",
Referenced: true,
}, },
&Assignment{ &Assignment{
Name: "baz", Name: "baz",
@ -602,155 +584,26 @@ var validParseTestCases = []struct {
Value: &Operator{ Value: &Operator{
OperatorPos: mkpos(41, 4, 13), OperatorPos: mkpos(41, 4, 13),
Operator: '+', Operator: '+',
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuffstuff",
},
Args: [2]Expression{ Args: [2]Expression{
&Variable{ &Variable{
Name: "foo", Name: "foo",
NamePos: mkpos(37, 4, 9), NamePos: mkpos(37, 4, 9),
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuff",
},
}, },
&Variable{ &Variable{
Name: "bar", Name: "bar",
NamePos: mkpos(43, 4, 15), NamePos: mkpos(43, 4, 15),
Value: &Variable{
Name: "foo",
NamePos: mkpos(25, 3, 9),
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuff",
},
},
}, },
}, },
}, },
OrigValue: &Operator{ Assigner: "=",
OperatorPos: mkpos(41, 4, 13),
Operator: '+',
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuffstuff",
},
Args: [2]Expression{
&Variable{
Name: "foo",
NamePos: mkpos(37, 4, 9),
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuff",
},
},
&Variable{
Name: "bar",
NamePos: mkpos(43, 4, 15),
Value: &Variable{
Name: "foo",
NamePos: mkpos(25, 3, 9),
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuff",
},
},
},
},
},
Assigner: "=",
Referenced: true,
}, },
&Assignment{ &Assignment{
Name: "boo", Name: "boo",
NamePos: mkpos(49, 5, 3), NamePos: mkpos(49, 5, 3),
EqualsPos: mkpos(53, 5, 7), EqualsPos: mkpos(53, 5, 7),
Value: &Operator{ Value: &Variable{
Args: [2]Expression{
&Variable{
Name: "baz",
NamePos: mkpos(55, 5, 9),
Value: &Operator{
OperatorPos: mkpos(41, 4, 13),
Operator: '+',
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuffstuff",
},
Args: [2]Expression{
&Variable{
Name: "foo",
NamePos: mkpos(37, 4, 9),
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuff",
},
},
&Variable{
Name: "bar",
NamePos: mkpos(43, 4, 15),
Value: &Variable{
Name: "foo",
NamePos: mkpos(25, 3, 9),
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuff",
},
},
},
},
},
},
&Variable{
Name: "foo",
NamePos: mkpos(68, 6, 10),
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuff",
},
},
},
OperatorPos: mkpos(66, 6, 8),
Operator: '+',
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuffstuffstuff",
},
},
OrigValue: &Variable{
Name: "baz", Name: "baz",
NamePos: mkpos(55, 5, 9), NamePos: mkpos(55, 5, 9),
Value: &Operator{
OperatorPos: mkpos(41, 4, 13),
Operator: '+',
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuffstuff",
},
Args: [2]Expression{
&Variable{
Name: "foo",
NamePos: mkpos(37, 4, 9),
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuff",
},
},
&Variable{
Name: "bar",
NamePos: mkpos(43, 4, 15),
Value: &Variable{
Name: "foo",
NamePos: mkpos(25, 3, 9),
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuff",
},
},
},
},
},
}, },
Assigner: "=", Assigner: "=",
}, },
@ -761,18 +614,6 @@ var validParseTestCases = []struct {
Value: &Variable{ Value: &Variable{
Name: "foo", Name: "foo",
NamePos: mkpos(68, 6, 10), NamePos: mkpos(68, 6, 10),
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuff",
},
},
OrigValue: &Variable{
Name: "foo",
NamePos: mkpos(68, 6, 10),
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuff",
},
}, },
Assigner: "+=", Assigner: "+=",
}, },
@ -791,10 +632,6 @@ var validParseTestCases = []struct {
Value: &Operator{ Value: &Operator{
OperatorPos: mkpos(12, 2, 12), OperatorPos: mkpos(12, 2, 12),
Operator: '+', Operator: '+',
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: -3,
},
Args: [2]Expression{ Args: [2]Expression{
&Int64{ &Int64{
LiteralPos: mkpos(9, 2, 9), LiteralPos: mkpos(9, 2, 9),
@ -804,10 +641,6 @@ var validParseTestCases = []struct {
&Operator{ &Operator{
OperatorPos: mkpos(17, 2, 17), OperatorPos: mkpos(17, 2, 17),
Operator: '+', Operator: '+',
Value: &Int64{
LiteralPos: mkpos(14, 2, 14),
Value: 1,
},
Args: [2]Expression{ Args: [2]Expression{
&Int64{ &Int64{
LiteralPos: mkpos(14, 2, 14), LiteralPos: mkpos(14, 2, 14),
@ -823,43 +656,7 @@ var validParseTestCases = []struct {
}, },
}, },
}, },
OrigValue: &Operator{ Assigner: "=",
OperatorPos: mkpos(12, 2, 12),
Operator: '+',
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: -3,
},
Args: [2]Expression{
&Int64{
LiteralPos: mkpos(9, 2, 9),
Value: -4,
Token: "-4",
},
&Operator{
OperatorPos: mkpos(17, 2, 17),
Operator: '+',
Value: &Int64{
LiteralPos: mkpos(14, 2, 14),
Value: 1,
},
Args: [2]Expression{
&Int64{
LiteralPos: mkpos(14, 2, 14),
Value: -5,
Token: "-5",
},
&Int64{
LiteralPos: mkpos(19, 2, 19),
Value: 6,
Token: "6",
},
},
},
},
},
Assigner: "=",
Referenced: false,
}, },
}, },
nil, nil,
@ -882,13 +679,7 @@ var validParseTestCases = []struct {
Value: 1000000, Value: 1000000,
Token: "1000000", Token: "1000000",
}, },
OrigValue: &Int64{ Assigner: "=",
LiteralPos: mkpos(9, 2, 9),
Value: 1000000,
Token: "1000000",
},
Assigner: "=",
Referenced: true,
}, },
&Assignment{ &Assignment{
Name: "bar", Name: "bar",
@ -897,23 +688,8 @@ var validParseTestCases = []struct {
Value: &Variable{ Value: &Variable{
Name: "foo", Name: "foo",
NamePos: mkpos(25, 3, 9), NamePos: mkpos(25, 3, 9),
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: 1000000,
Token: "1000000",
},
}, },
OrigValue: &Variable{ Assigner: "=",
Name: "foo",
NamePos: mkpos(25, 3, 9),
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: 1000000,
Token: "1000000",
},
},
Assigner: "=",
Referenced: true,
}, },
&Assignment{ &Assignment{
Name: "baz", Name: "baz",
@ -922,164 +698,26 @@ var validParseTestCases = []struct {
Value: &Operator{ Value: &Operator{
OperatorPos: mkpos(41, 4, 13), OperatorPos: mkpos(41, 4, 13),
Operator: '+', Operator: '+',
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: 2000000,
},
Args: [2]Expression{ Args: [2]Expression{
&Variable{ &Variable{
Name: "foo", Name: "foo",
NamePos: mkpos(37, 4, 9), NamePos: mkpos(37, 4, 9),
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: 1000000,
Token: "1000000",
},
}, },
&Variable{ &Variable{
Name: "bar", Name: "bar",
NamePos: mkpos(43, 4, 15), NamePos: mkpos(43, 4, 15),
Value: &Variable{
Name: "foo",
NamePos: mkpos(25, 3, 9),
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: 1000000,
Token: "1000000",
},
},
}, },
}, },
}, },
OrigValue: &Operator{ Assigner: "=",
OperatorPos: mkpos(41, 4, 13),
Operator: '+',
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: 2000000,
},
Args: [2]Expression{
&Variable{
Name: "foo",
NamePos: mkpos(37, 4, 9),
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: 1000000,
Token: "1000000",
},
},
&Variable{
Name: "bar",
NamePos: mkpos(43, 4, 15),
Value: &Variable{
Name: "foo",
NamePos: mkpos(25, 3, 9),
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: 1000000,
Token: "1000000",
},
},
},
},
},
Assigner: "=",
Referenced: true,
}, },
&Assignment{ &Assignment{
Name: "boo", Name: "boo",
NamePos: mkpos(49, 5, 3), NamePos: mkpos(49, 5, 3),
EqualsPos: mkpos(53, 5, 7), EqualsPos: mkpos(53, 5, 7),
Value: &Operator{ Value: &Variable{
Args: [2]Expression{
&Variable{
Name: "baz",
NamePos: mkpos(55, 5, 9),
Value: &Operator{
OperatorPos: mkpos(41, 4, 13),
Operator: '+',
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: 2000000,
},
Args: [2]Expression{
&Variable{
Name: "foo",
NamePos: mkpos(37, 4, 9),
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: 1000000,
Token: "1000000",
},
},
&Variable{
Name: "bar",
NamePos: mkpos(43, 4, 15),
Value: &Variable{
Name: "foo",
NamePos: mkpos(25, 3, 9),
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: 1000000,
Token: "1000000",
},
},
},
},
},
},
&Variable{
Name: "foo",
NamePos: mkpos(68, 6, 10),
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: 1000000,
Token: "1000000",
},
},
},
OperatorPos: mkpos(66, 6, 8),
Operator: '+',
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: 3000000,
},
},
OrigValue: &Variable{
Name: "baz", Name: "baz",
NamePos: mkpos(55, 5, 9), NamePos: mkpos(55, 5, 9),
Value: &Operator{
OperatorPos: mkpos(41, 4, 13),
Operator: '+',
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: 2000000,
},
Args: [2]Expression{
&Variable{
Name: "foo",
NamePos: mkpos(37, 4, 9),
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: 1000000,
Token: "1000000",
},
},
&Variable{
Name: "bar",
NamePos: mkpos(43, 4, 15),
Value: &Variable{
Name: "foo",
NamePos: mkpos(25, 3, 9),
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: 1000000,
Token: "1000000",
},
},
},
},
},
}, },
Assigner: "=", Assigner: "=",
}, },
@ -1090,20 +728,6 @@ var validParseTestCases = []struct {
Value: &Variable{ Value: &Variable{
Name: "foo", Name: "foo",
NamePos: mkpos(68, 6, 10), NamePos: mkpos(68, 6, 10),
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: 1000000,
Token: "1000000",
},
},
OrigValue: &Variable{
Name: "foo",
NamePos: mkpos(68, 6, 10),
Value: &Int64{
LiteralPos: mkpos(9, 2, 9),
Value: 1000000,
Token: "1000000",
},
}, },
Assigner: "+=", Assigner: "+=",
}, },
@ -1171,7 +795,7 @@ func TestParseValidInput(t *testing.T) {
for i, testCase := range validParseTestCases { for i, testCase := range validParseTestCases {
t.Run(strconv.Itoa(i), func(t *testing.T) { t.Run(strconv.Itoa(i), func(t *testing.T) {
r := bytes.NewBufferString(testCase.input) r := bytes.NewBufferString(testCase.input)
file, errs := ParseAndEval("", r, NewScope(nil)) file, errs := Parse("", r)
if len(errs) != 0 { if len(errs) != 0 {
t.Errorf("test case: %s", testCase.input) t.Errorf("test case: %s", testCase.input)
t.Errorf("unexpected errors:") t.Errorf("unexpected errors:")
@ -1236,6 +860,17 @@ func TestParserError(t *testing.T) {
`, `,
err: "Duplicate select condition found: arch()", err: "Duplicate select condition found: arch()",
}, },
{
name: "select with duplicate binding",
input: `
m {
foo: select((arch(), os()), {
(any @ bar, any @ bar): true,
}),
}
`,
err: "Found duplicate select pattern binding: bar",
},
// TODO: test more parser errors // TODO: test more parser errors
} }
@ -1284,7 +919,7 @@ func TestParserEndPos(t *testing.T) {
r := bytes.NewBufferString(in) r := bytes.NewBufferString(in)
file, errs := ParseAndEval("", r, NewScope(nil)) file, errs := Parse("", r)
if len(errs) != 0 { if len(errs) != 0 {
t.Errorf("unexpected errors:") t.Errorf("unexpected errors:")
for _, err := range errs { for _, err := range errs {
@ -1318,9 +953,8 @@ func TestParserEndPos(t *testing.T) {
func TestParserNotEvaluated(t *testing.T) { func TestParserNotEvaluated(t *testing.T) {
// When parsing without evaluation, create variables correctly // When parsing without evaluation, create variables correctly
scope := NewScope(nil)
input := "FOO=abc\n" input := "FOO=abc\n"
_, errs := Parse("", bytes.NewBufferString(input), scope) file, errs := Parse("", bytes.NewBufferString(input))
if errs != nil { if errs != nil {
t.Errorf("unexpected errors:") t.Errorf("unexpected errors:")
for _, err := range errs { for _, err := range errs {
@ -1328,11 +962,11 @@ func TestParserNotEvaluated(t *testing.T) {
} }
t.FailNow() t.FailNow()
} }
assignment, found := scope.Get("FOO") assignment, ok := file.Defs[0].(*Assignment)
if !found { if !ok || assignment.Name != "FOO" {
t.Fatalf("Expected to find FOO after parsing %s", input) t.Fatalf("Expected to find FOO after parsing %s", input)
} }
if s := assignment.String(); strings.Contains(s, "PANIC") { if assignment.Value.String() != "abc" {
t.Errorf("Attempt to print FOO returned %s", s) t.Errorf("Attempt to print FOO returned %s", assignment.Value.String())
} }
} }

View file

@ -99,7 +99,7 @@ func (p *printer) printAssignment(assignment *Assignment) {
p.requestSpace() p.requestSpace()
p.printToken(assignment.Assigner, assignment.EqualsPos) p.printToken(assignment.Assigner, assignment.EqualsPos)
p.requestSpace() p.requestSpace()
p.printExpression(assignment.OrigValue) p.printExpression(assignment.Value)
p.requestNewline() p.requestNewline()
} }
@ -134,7 +134,7 @@ func (p *printer) printExpression(value Expression) {
case *Select: case *Select:
p.printSelect(v) p.printSelect(v)
default: default:
panic(fmt.Errorf("bad property type: %s", value.Type())) panic(fmt.Errorf("bad property type: %v", value))
} }
} }
@ -143,7 +143,7 @@ func (p *printer) printSelect(s *Select) {
return return
} }
if len(s.Cases) == 1 && len(s.Cases[0].Patterns) == 1 { if len(s.Cases) == 1 && len(s.Cases[0].Patterns) == 1 {
if str, ok := s.Cases[0].Patterns[0].(*String); ok && str.Value == default_select_branch_name { if str, ok := s.Cases[0].Patterns[0].Value.(*String); ok && str.Value == default_select_branch_name {
p.printExpression(s.Cases[0].Value) p.printExpression(s.Cases[0].Value)
p.pos = s.RBracePos p.pos = s.RBracePos
return return
@ -196,22 +196,7 @@ func (p *printer) printSelect(s *Select) {
p.printToken("(", p.pos) p.printToken("(", p.pos)
} }
for i, pat := range c.Patterns { for i, pat := range c.Patterns {
switch pat := pat.(type) { p.printSelectPattern(pat)
case *String:
if pat.Value != default_select_branch_name {
p.printToken(strconv.Quote(pat.Value), pat.LiteralPos)
} else {
p.printToken("default", pat.LiteralPos)
}
case *Bool:
s := "false"
if pat.Value {
s = "true"
}
p.printToken(s, pat.LiteralPos)
default:
panic("Unhandled case")
}
if i < len(c.Patterns)-1 { if i < len(c.Patterns)-1 {
p.printToken(",", p.pos) p.printToken(",", p.pos)
p.requestSpace() p.requestSpace()
@ -222,7 +207,7 @@ func (p *printer) printSelect(s *Select) {
} }
p.printToken(":", c.ColonPos) p.printToken(":", c.ColonPos)
p.requestSpace() p.requestSpace()
if unset, ok := c.Value.(UnsetProperty); ok { if unset, ok := c.Value.(*UnsetProperty); ok {
p.printToken(unset.String(), unset.Pos()) p.printToken(unset.String(), unset.Pos())
} else { } else {
p.printExpression(c.Value) p.printExpression(c.Value)
@ -240,6 +225,33 @@ func (p *printer) printSelect(s *Select) {
} }
} }
func (p *printer) printSelectPattern(pat SelectPattern) {
switch pat := pat.Value.(type) {
case *String:
if pat.Value == default_select_branch_name {
p.printToken("default", pat.LiteralPos)
} else if pat.Value == any_select_branch_name {
p.printToken("any", pat.LiteralPos)
} else {
p.printToken(strconv.Quote(pat.Value), pat.LiteralPos)
}
case *Bool:
s := "false"
if pat.Value {
s = "true"
}
p.printToken(s, pat.LiteralPos)
default:
panic("Unhandled case")
}
if pat.Binding.Name != "" {
p.requestSpace()
p.printToken("@", pat.Binding.Pos())
p.requestSpace()
p.printExpression(&pat.Binding)
}
}
func (p *printer) printList(list []Expression, pos, endPos scanner.Position) { func (p *printer) printList(list []Expression, pos, endPos scanner.Position) {
p.requestSpace() p.requestSpace()
p.printToken("[", pos) p.printToken("[", pos)

View file

@ -733,6 +733,26 @@ foo {
default: [], default: [],
}), }),
} }
`,
},
{
name: "Select with bindings",
input: `
foo {
stuff: select(arch(), {
"x86": "a",
any
@ baz: "b" + baz,
}),
}
`,
output: `
foo {
stuff: select(arch(), {
"x86": "a",
any @ baz: "b" + baz,
}),
}
`, `,
}, },
} }
@ -744,7 +764,7 @@ func TestPrinter(t *testing.T) {
expected := testCase.output[1:] expected := testCase.output[1:]
r := bytes.NewBufferString(in) r := bytes.NewBufferString(in)
file, errs := Parse("", r, NewScope(nil)) file, errs := Parse("", r)
if len(errs) != 0 { if len(errs) != 0 {
t.Errorf("test case: %s", in) t.Errorf("test case: %s", in)
t.Errorf("unexpected errors:") t.Errorf("unexpected errors:")

View file

@ -282,8 +282,8 @@ func isListOfPrimitives(values []Expression) bool {
if len(values) == 0 { if len(values) == 0 {
return true return true
} }
switch values[0].Type() { switch values[0].(type) {
case BoolType, StringType, Int64Type: case *Bool, *String, *Int64:
return true return true
default: default:
return false return false

View file

@ -21,6 +21,7 @@ import (
"strings" "strings"
"github.com/google/blueprint/optional" "github.com/google/blueprint/optional"
"github.com/google/blueprint/parser"
) )
// ConfigurableOptional is the same as ShallowOptional, but we use this separate // ConfigurableOptional is the same as ShallowOptional, but we use this separate
@ -150,6 +151,17 @@ type ConfigurableValue struct {
boolValue bool boolValue bool
} }
func (c *ConfigurableValue) toExpression() parser.Expression {
switch c.typ {
case configurableValueTypeBool:
return &parser.Bool{Value: c.boolValue}
case configurableValueTypeString:
return &parser.String{Value: c.stringValue}
default:
panic(fmt.Sprintf("Unhandled configurableValueType: %s", c.typ.String()))
}
}
func (c *ConfigurableValue) String() string { func (c *ConfigurableValue) String() string {
switch c.typ { switch c.typ {
case configurableValueTypeString: case configurableValueTypeString:
@ -193,6 +205,7 @@ const (
configurablePatternTypeString configurablePatternType = iota configurablePatternTypeString configurablePatternType = iota
configurablePatternTypeBool configurablePatternTypeBool
configurablePatternTypeDefault configurablePatternTypeDefault
configurablePatternTypeAny
) )
func (v *configurablePatternType) String() string { func (v *configurablePatternType) String() string {
@ -203,6 +216,8 @@ func (v *configurablePatternType) String() string {
return "bool" return "bool"
case configurablePatternTypeDefault: case configurablePatternTypeDefault:
return "default" return "default"
case configurablePatternTypeAny:
return "any"
default: default:
panic("unimplemented") panic("unimplemented")
} }
@ -222,6 +237,7 @@ type ConfigurablePattern struct {
typ configurablePatternType typ configurablePatternType
stringValue string stringValue string
boolValue bool boolValue bool
binding string
} }
func NewStringConfigurablePattern(s string) ConfigurablePattern { func NewStringConfigurablePattern(s string) ConfigurablePattern {
@ -251,6 +267,9 @@ func (p *ConfigurablePattern) matchesValue(v ConfigurableValue) bool {
if v.typ == configurableValueTypeUndefined { if v.typ == configurableValueTypeUndefined {
return false return false
} }
if p.typ == configurablePatternTypeAny {
return true
}
if p.typ != v.typ.patternType() { if p.typ != v.typ.patternType() {
return false return false
} }
@ -271,6 +290,9 @@ func (p *ConfigurablePattern) matchesValueType(v ConfigurableValue) bool {
if v.typ == configurableValueTypeUndefined { if v.typ == configurableValueTypeUndefined {
return true return true
} }
if p.typ == configurablePatternTypeAny {
return true
}
return p.typ == v.typ.patternType() return p.typ == v.typ.patternType()
} }
@ -282,27 +304,46 @@ func (p *ConfigurablePattern) matchesValueType(v ConfigurableValue) bool {
// different configurable properties. // different configurable properties.
type ConfigurableCase[T ConfigurableElements] struct { type ConfigurableCase[T ConfigurableElements] struct {
patterns []ConfigurablePattern patterns []ConfigurablePattern
value *T value parser.Expression
} }
type configurableCaseReflection interface { type configurableCaseReflection interface {
initialize(patterns []ConfigurablePattern, value interface{}) initialize(patterns []ConfigurablePattern, value parser.Expression)
} }
var _ configurableCaseReflection = &ConfigurableCase[string]{} var _ configurableCaseReflection = &ConfigurableCase[string]{}
func NewConfigurableCase[T ConfigurableElements](patterns []ConfigurablePattern, value *T) ConfigurableCase[T] { func NewConfigurableCase[T ConfigurableElements](patterns []ConfigurablePattern, value *T) ConfigurableCase[T] {
var valueExpr parser.Expression
if value == nil {
valueExpr = &parser.UnsetProperty{}
} else {
switch v := any(value).(type) {
case *string:
valueExpr = &parser.String{Value: *v}
case *bool:
valueExpr = &parser.Bool{Value: *v}
case *[]string:
innerValues := make([]parser.Expression, 0, len(*v))
for _, x := range *v {
innerValues = append(innerValues, &parser.String{Value: x})
}
valueExpr = &parser.List{Values: innerValues}
default:
panic(fmt.Sprintf("should be unreachable due to the ConfigurableElements restriction: %#v", value))
}
}
// Clone the values so they can't be modified from soong // Clone the values so they can't be modified from soong
patterns = slices.Clone(patterns) patterns = slices.Clone(patterns)
return ConfigurableCase[T]{ return ConfigurableCase[T]{
patterns: patterns, patterns: patterns,
value: copyConfiguredValuePtr(value), value: valueExpr,
} }
} }
func (c *ConfigurableCase[T]) initialize(patterns []ConfigurablePattern, value interface{}) { func (c *ConfigurableCase[T]) initialize(patterns []ConfigurablePattern, value parser.Expression) {
c.patterns = patterns c.patterns = patterns
c.value = value.(*T) c.value = value
} }
// for the given T, return the reflect.type of configurableCase[T] // for the given T, return the reflect.type of configurableCase[T]
@ -371,6 +412,22 @@ type Configurable[T ConfigurableElements] struct {
marker configurableMarker marker configurableMarker
propertyName string propertyName string
inner *configurableInner[T] 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 { type configurableInner[T ConfigurableElements] struct {
@ -384,6 +441,7 @@ type configurableInner[T ConfigurableElements] struct {
type singleConfigurable[T ConfigurableElements] struct { type singleConfigurable[T ConfigurableElements] struct {
conditions []ConfigurableCondition conditions []ConfigurableCondition
cases []ConfigurableCase[T] cases []ConfigurableCase[T]
scope *parser.Scope
} }
// Ignore the warning about the unused marker variable, it's used via reflection // Ignore the warning about the unused marker variable, it's used via reflection
@ -398,6 +456,7 @@ func NewConfigurable[T ConfigurableElements](conditions []ConfigurableCondition,
// Clone the slices so they can't be modified from soong // Clone the slices so they can't be modified from soong
conditions = slices.Clone(conditions) conditions = slices.Clone(conditions)
cases = slices.Clone(cases) cases = slices.Clone(cases)
var zeroPostProcessors [][]postProcessor[T]
return Configurable[T]{ return Configurable[T]{
inner: &configurableInner[T]{ inner: &configurableInner[T]{
single: singleConfigurable[T]{ single: singleConfigurable[T]{
@ -405,35 +464,81 @@ func NewConfigurable[T ConfigurableElements](conditions []ConfigurableCondition,
cases: cases, 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) { func (c *Configurable[T]) AppendSimpleValue(value T) {
value = copyConfiguredValue(value) value = copyConfiguredValue(value)
// This may be a property that was never initialized from a bp file // This may be a property that was never initialized from a bp file
if c.inner == nil { if c.inner == nil {
c.inner = &configurableInner[T]{ c.initialize(nil, "", nil, []ConfigurableCase[T]{{
single: singleConfigurable[T]{ value: configuredValueToExpression(value),
cases: []ConfigurableCase[T]{{ }})
value: &value,
}},
},
}
return return
} }
c.inner.appendSimpleValue(value) 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 {
var nilCases []ConfigurableCase[T]
c.initialize(nil, "", nil, nilCases)
}
if 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. // Get returns the final value for the configurable property.
// A configurable property may be unset, in which case Get will return nil. // A configurable property may be unset, in which case Get will return nil.
func (c *Configurable[T]) Get(evaluator ConfigurableEvaluator) ConfigurableOptional[T] { 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) return configuredValuePtrToOptional(result)
} }
// GetOrDefault is the same as Get, but will return the provided default value if the property was unset. // 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 { 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 { if result != nil {
// Copy the result so that it can't be changed from soong // Copy the result so that it can't be changed from soong
return copyConfiguredValue(*result) return copyConfiguredValue(*result)
@ -441,6 +546,127 @@ func (c *Configurable[T]) GetOrDefault(evaluator ConfigurableEvaluator, defaultV
return defaultValue 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 { func (c *configurableInner[T]) evaluate(propertyName string, evaluator ConfigurableEvaluator) *T {
if c == nil { if c == nil {
return nil return nil
@ -472,7 +698,12 @@ func (c *singleConfigurable[T]) evaluateNonTransitive(propertyName string, evalu
if len(c.cases) == 0 { if len(c.cases) == 0 {
return nil return nil
} else if len(c.cases) == 1 { } else if len(c.cases) == 1 {
return c.cases[0].value if result, err := expressionToConfiguredValue[T](c.cases[0].value, c.scope); err != nil {
evaluator.PropertyErrorf(propertyName, "%s", err.Error())
return nil
} else {
return result
}
} else { } else {
evaluator.PropertyErrorf(propertyName, "Expected 0 or 1 branches in an unconfigured select, found %d", len(c.cases)) evaluator.PropertyErrorf(propertyName, "Expected 0 or 1 branches in an unconfigured select, found %d", len(c.cases))
return nil return nil
@ -499,7 +730,13 @@ func (c *singleConfigurable[T]) evaluateNonTransitive(propertyName string, evalu
} }
} }
if allMatch && !foundMatch { if allMatch && !foundMatch {
result = case_.value newScope := createScopeWithBindings(c.scope, case_.patterns, values)
if r, err := expressionToConfiguredValue[T](case_.value, newScope); err != nil {
evaluator.PropertyErrorf(propertyName, "%s", err.Error())
return nil
} else {
result = r
}
foundMatch = true foundMatch = true
} }
} }
@ -511,6 +748,27 @@ func (c *singleConfigurable[T]) evaluateNonTransitive(propertyName string, evalu
return nil return nil
} }
func createScopeWithBindings(parent *parser.Scope, patterns []ConfigurablePattern, values []ConfigurableValue) *parser.Scope {
result := parent
for i, pattern := range patterns {
if pattern.binding != "" {
if result == parent {
result = parser.NewScope(parent)
}
err := result.HandleAssignment(&parser.Assignment{
Name: pattern.binding,
Value: values[i].toExpression(),
Assigner: "=",
})
if err != nil {
// This shouldn't happen due to earlier validity checks
panic(err.Error())
}
}
}
return result
}
func appendConfiguredValues[T ConfigurableElements](a, b *T) *T { func appendConfiguredValues[T ConfigurableElements](a, b *T) *T {
if a == nil && b == nil { if a == nil && b == nil {
return nil return nil
@ -579,20 +837,27 @@ type configurableReflection interface {
// Same as configurableReflection, but since initialize needs to take a pointer // Same as configurableReflection, but since initialize needs to take a pointer
// to a Configurable, it was broken out into a separate interface. // to a Configurable, it was broken out into a separate interface.
type configurablePtrReflection interface { type configurablePtrReflection interface {
initialize(propertyName string, conditions []ConfigurableCondition, cases any) initialize(scope *parser.Scope, propertyName string, conditions []ConfigurableCondition, cases any)
} }
var _ configurableReflection = Configurable[string]{} var _ configurableReflection = Configurable[string]{}
var _ configurablePtrReflection = &Configurable[string]{} var _ configurablePtrReflection = &Configurable[string]{}
func (c *Configurable[T]) initialize(propertyName string, conditions []ConfigurableCondition, cases any) { func (c *Configurable[T]) initialize(scope *parser.Scope, propertyName string, conditions []ConfigurableCondition, cases any) {
c.propertyName = propertyName c.propertyName = propertyName
c.inner = &configurableInner[T]{ c.inner = &configurableInner[T]{
single: singleConfigurable[T]{ single: singleConfigurable[T]{
conditions: conditions, conditions: conditions,
cases: cases.([]ConfigurableCase[T]), cases: cases.([]ConfigurableCase[T]),
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) { func (c Configurable[T]) setAppend(append any, replace bool, prepend bool) {
@ -600,12 +865,37 @@ func (c Configurable[T]) setAppend(append any, replace bool, prepend bool) {
if a.inner.isEmpty() { if a.inner.isEmpty() {
return 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) c.inner.setAppend(a.inner, replace, prepend)
if c.inner == c.inner.next { if c.inner == c.inner.next {
panic("pointer loop") 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) { func (c *configurableInner[T]) setAppend(append *configurableInner[T], replace bool, prepend bool) {
if c.isEmpty() { if c.isEmpty() {
*c = *append.clone() *c = *append.clone()
@ -644,13 +934,21 @@ 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) { func (c *configurableInner[T]) appendSimpleValue(value T) {
if c.next == nil { if c.next == nil {
c.replace = false c.replace = false
c.next = &configurableInner[T]{ c.next = &configurableInner[T]{
single: singleConfigurable[T]{ single: singleConfigurable[T]{
cases: []ConfigurableCase[T]{{ cases: []ConfigurableCase[T]{{
value: &value, value: configuredValueToExpression(value),
}}, }},
}, },
} }
@ -678,46 +976,28 @@ func (c *singleConfigurable[T]) printfInto(value string) error {
if c.value == nil { if c.value == nil {
continue continue
} }
switch v := any(c.value).(type) { if err := c.value.PrintfInto(value); err != nil {
case *string: return err
if err := printfIntoString(v, value); err != nil {
return err
}
case *[]string:
for i := range *v {
if err := printfIntoString(&((*v)[i]), value); err != nil {
return err
}
}
} }
} }
return nil return nil
} }
func printfIntoString(s *string, configValue string) error { func (c Configurable[T]) clone() any {
count := strings.Count(*s, "%") var newPostProcessors *[][]postProcessor[T]
if count == 0 { if c.postProcessors != nil {
return nil x := appendPostprocessors(*c.postProcessors, nil, 0)
newPostProcessors = &x
} }
return Configurable[T]{
if count > 1 { propertyName: c.propertyName,
return fmt.Errorf("list/value variable properties only support a single '%%'") inner: c.inner.clone(),
postProcessors: newPostProcessors,
} }
if !strings.Contains(*s, "%s") {
return fmt.Errorf("unsupported %% in value variable property")
}
*s = fmt.Sprintf(*s, configValue)
return nil
} }
func (c Configurable[T]) clone() any { func (c Configurable[T]) Clone() Configurable[T] {
return Configurable[T]{ return c.clone().(Configurable[T])
propertyName: c.propertyName,
inner: c.inner.clone(),
}
} }
func (c *configurableInner[T]) clone() *configurableInner[T] { func (c *configurableInner[T]) clone() *configurableInner[T] {
@ -755,6 +1035,9 @@ func (c *singleConfigurable[T]) isEmpty() bool {
return false return false
} }
if len(c.cases) == 1 && c.cases[0].value != nil { if len(c.cases) == 1 && c.cases[0].value != nil {
if _, ok := c.cases[0].value.(*parser.UnsetProperty); ok {
return true
}
return false return false
} }
return true return true
@ -774,7 +1057,7 @@ func (c *singleConfigurable[T]) alwaysHasValue() bool {
return false return false
} }
for _, c := range c.cases { for _, c := range c.cases {
if c.value == nil { if _, isUnset := c.value.(*parser.UnsetProperty); isUnset || c.value == nil {
return false return false
} }
} }
@ -785,17 +1068,84 @@ func (c Configurable[T]) configuredType() reflect.Type {
return reflect.TypeOf((*T)(nil)).Elem() return reflect.TypeOf((*T)(nil)).Elem()
} }
func copyConfiguredValuePtr[T ConfigurableElements](t *T) *T { func expressionToConfiguredValue[T ConfigurableElements](expr parser.Expression, scope *parser.Scope) (*T, error) {
if t == nil { expr, err := expr.Eval(scope)
return nil if err != nil {
return nil, err
} }
switch t2 := any(*t).(type) { switch e := expr.(type) {
case []string: case *parser.UnsetProperty:
result := any(slices.Clone(t2)).(T) return nil, nil
return &result case *parser.String:
if result, ok := any(&e.Value).(*T); ok {
return result, nil
} else {
return nil, fmt.Errorf("can't assign string value to %s property", configuredTypeToString[T]())
}
case *parser.Bool:
if result, ok := any(&e.Value).(*T); ok {
return result, nil
} else {
return nil, fmt.Errorf("can't assign bool value to %s property", configuredTypeToString[T]())
}
case *parser.List:
result := make([]string, 0, len(e.Values))
for _, x := range e.Values {
if y, ok := x.(*parser.String); ok {
result = append(result, y.Value)
} else {
return nil, fmt.Errorf("expected list of strings but found list of %s", x.Type())
}
}
if result, ok := any(&result).(*T); ok {
return result, nil
} else {
return nil, fmt.Errorf("can't assign list of strings to list of %s property", configuredTypeToString[T]())
}
default: default:
x := *t // If the expression was not evaluated beforehand we could hit this error even when the types match,
return &x // but that's an internal logic error.
return nil, fmt.Errorf("expected %s but found %s (%#v)", configuredTypeToString[T](), expr.Type().String(), expr)
}
}
func configuredValueToExpression[T ConfigurableElements](value T) parser.Expression {
switch v := any(value).(type) {
case string:
return &parser.String{Value: v}
case bool:
return &parser.Bool{Value: v}
case []string:
values := make([]parser.Expression, 0, len(v))
for _, x := range v {
values = append(values, &parser.String{Value: x})
}
return &parser.List{Values: values}
default:
panic("unhandled type in configuredValueToExpression")
}
}
func configuredTypeToString[T ConfigurableElements]() string {
var zero T
switch any(zero).(type) {
case string:
return "string"
case bool:
return "bool"
case []string:
return "list of strings"
default:
panic("should be unreachable")
}
}
func copyConfiguredValue[T ConfigurableElements](t T) T {
switch t2 := any(t).(type) {
case []string:
return any(slices.Clone(t2)).(T)
default:
return t
} }
} }
@ -812,18 +1162,75 @@ func configuredValuePtrToOptional[T ConfigurableElements](t *T) ConfigurableOpti
} }
} }
func copyConfiguredValue[T ConfigurableElements](t T) T {
switch t2 := any(t).(type) {
case []string:
return any(slices.Clone(t2)).(T)
default:
return t
}
}
// PrintfIntoConfigurable replaces %s occurrences in strings in Configurable properties // PrintfIntoConfigurable replaces %s occurrences in strings in Configurable properties
// with the provided string value. It's intention is to support soong config value variables // with the provided string value. It's intention is to support soong config value variables
// on Configurable properties. // on Configurable properties.
func PrintfIntoConfigurable(c any, value string) error { func PrintfIntoConfigurable(c any, value string) error {
return c.(configurableReflection).printfInto(value) return c.(configurableReflection).printfInto(value)
} }
func promoteValueToConfigurable(origional reflect.Value) reflect.Value {
var expr parser.Expression
var kind reflect.Kind
if origional.Kind() == reflect.Pointer && origional.IsNil() {
expr = &parser.UnsetProperty{}
kind = origional.Type().Elem().Kind()
} else {
if origional.Kind() == reflect.Pointer {
origional = origional.Elem()
}
kind = origional.Kind()
switch kind {
case reflect.String:
expr = &parser.String{Value: origional.String()}
case reflect.Bool:
expr = &parser.Bool{Value: origional.Bool()}
case reflect.Slice:
strList := origional.Interface().([]string)
exprList := make([]parser.Expression, 0, len(strList))
for _, x := range strList {
exprList = append(exprList, &parser.String{Value: x})
}
expr = &parser.List{Values: exprList}
default:
panic("can only convert string/bool/[]string to configurable")
}
}
switch kind {
case reflect.String:
return reflect.ValueOf(Configurable[string]{
inner: &configurableInner[string]{
single: singleConfigurable[string]{
cases: []ConfigurableCase[string]{{
value: expr,
}},
},
},
postProcessors: &[][]postProcessor[string]{},
})
case reflect.Bool:
return reflect.ValueOf(Configurable[bool]{
inner: &configurableInner[bool]{
single: singleConfigurable[bool]{
cases: []ConfigurableCase[bool]{{
value: expr,
}},
},
},
postProcessors: &[][]postProcessor[bool]{},
})
case reflect.Slice:
return reflect.ValueOf(Configurable[[]string]{
inner: &configurableInner[[]string]{
single: singleConfigurable[[]string]{
cases: []ConfigurableCase[[]string]{{
value: expr,
}},
},
},
postProcessors: &[][]postProcessor[[]string]{},
})
default:
panic(fmt.Sprintf("Can't convert %s property to a configurable", origional.Kind().String()))
}
}

View file

@ -0,0 +1,95 @@
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 TestPostProcessorWhenPassedToHelperFunction(t *testing.T) {
prop := NewConfigurable[[]string](nil, nil)
prop.AppendSimpleValue([]string{"a"})
prop.AppendSimpleValue([]string{"b"})
helper := func(p Configurable[[]string]) {
p.AddPostProcessor(addToElements("1"))
}
helper(prop)
expected := []string{"a1", "b1"}
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)

View file

@ -473,33 +473,7 @@ func ExtendBasicType(dstFieldValue, srcFieldValue reflect.Value, order Order) {
// structs when they want to change the default values of properties. // structs when they want to change the default values of properties.
srcFieldType := srcFieldValue.Type() srcFieldType := srcFieldValue.Type()
if isConfigurable(dstFieldValue.Type()) && !isConfigurable(srcFieldType) { if isConfigurable(dstFieldValue.Type()) && !isConfigurable(srcFieldType) {
var value reflect.Value srcFieldValue = promoteValueToConfigurable(srcFieldValue)
if srcFieldType.Kind() == reflect.Pointer {
srcFieldType = srcFieldType.Elem()
if srcFieldValue.IsNil() {
value = srcFieldValue
} else {
// Copy the pointer
value = reflect.New(srcFieldType)
value.Elem().Set(srcFieldValue.Elem())
}
} else {
value = reflect.New(srcFieldType)
value.Elem().Set(srcFieldValue)
}
caseType := configurableCaseType(srcFieldType)
case_ := reflect.New(caseType)
case_.Interface().(configurableCaseReflection).initialize(nil, value.Interface())
cases := reflect.MakeSlice(reflect.SliceOf(caseType), 0, 1)
cases = reflect.Append(cases, case_.Elem())
ct, err := configurableType(srcFieldType)
if err != nil {
// Should be unreachable due to earlier checks
panic(err.Error())
}
temp := reflect.New(ct)
temp.Interface().(configurablePtrReflection).initialize("", nil, cases.Interface())
srcFieldValue = temp.Elem()
} }
switch srcFieldValue.Kind() { switch srcFieldValue.Kind() {

View file

@ -20,6 +20,8 @@ import (
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
"github.com/google/blueprint/parser"
) )
type appendPropertyTestCase struct { type appendPropertyTestCase struct {
@ -1257,172 +1259,166 @@ func appendPropertiesTestCases() []appendPropertyTestCase {
{ {
name: "Append configurable", name: "Append configurable",
dst: &struct{ S Configurable[[]string] }{ dst: &struct{ S Configurable[[]string] }{
S: Configurable[[]string]{ S: NewConfigurable[[]string]([]ConfigurableCondition{{
inner: &configurableInner[[]string]{ functionName: "soong_config_variable",
single: singleConfigurable[[]string]{ args: []string{
conditions: []ConfigurableCondition{{ "my_namespace",
functionName: "soong_config_variable", "foo",
args: []string{
"my_namespace",
"foo",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: &[]string{"1", "2"},
}},
},
}, },
}, }},
[]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] }{ src: &struct{ S Configurable[[]string] }{
S: Configurable[[]string]{ S: NewConfigurable([]ConfigurableCondition{{
inner: &configurableInner[[]string]{ functionName: "release_variable",
single: singleConfigurable[[]string]{ args: []string{
conditions: []ConfigurableCondition{{ "bar",
functionName: "release_variable",
args: []string{
"bar",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "b",
}},
value: &[]string{"3", "4"},
}},
},
}, },
}, }},
[]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] }{ out: &struct{ S Configurable[[]string] }{
S: Configurable[[]string]{ S: func() Configurable[[]string] {
inner: &configurableInner[[]string]{ result := NewConfigurable([]ConfigurableCondition{{
single: singleConfigurable[[]string]{ functionName: "soong_config_variable",
conditions: []ConfigurableCondition{{ args: []string{
functionName: "soong_config_variable", "my_namespace",
args: []string{ "foo",
"my_namespace",
"foo",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: &[]string{"1", "2"},
}},
}, },
next: &configurableInner[[]string]{ }},
single: singleConfigurable[[]string]{ []ConfigurableCase[[]string]{{
conditions: []ConfigurableCondition{{ patterns: []ConfigurablePattern{{
functionName: "release_variable", typ: configurablePatternTypeString,
args: []string{ stringValue: "a",
"bar", }},
}, value: &parser.List{Values: []parser.Expression{
}}, &parser.String{Value: "1"},
cases: []ConfigurableCase[[]string]{{ &parser.String{Value: "2"},
patterns: []ConfigurablePattern{{ }},
typ: configurablePatternTypeString, }},
stringValue: "b", )
}}, result.Append(NewConfigurable([]ConfigurableCondition{{
value: &[]string{"3", "4"}, 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", name: "Prepend configurable",
order: Prepend, order: Prepend,
dst: &struct{ S Configurable[[]string] }{ dst: &struct{ S Configurable[[]string] }{
S: Configurable[[]string]{ S: NewConfigurable([]ConfigurableCondition{{
inner: &configurableInner[[]string]{ functionName: "soong_config_variable",
single: singleConfigurable[[]string]{ args: []string{
conditions: []ConfigurableCondition{{ "my_namespace",
functionName: "soong_config_variable", "foo",
args: []string{
"my_namespace",
"foo",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: &[]string{"1", "2"},
}},
},
}, },
}, }},
[]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] }{ src: &struct{ S Configurable[[]string] }{
S: Configurable[[]string]{ S: NewConfigurable([]ConfigurableCondition{{
inner: &configurableInner[[]string]{ functionName: "release_variable",
single: singleConfigurable[[]string]{ args: []string{
conditions: []ConfigurableCondition{{ "bar",
functionName: "release_variable",
args: []string{
"bar",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "b",
}},
value: &[]string{"3", "4"},
}},
},
}, },
}, }},
[]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] }{ out: &struct{ S Configurable[[]string] }{
S: Configurable[[]string]{ S: func() Configurable[[]string] {
inner: &configurableInner[[]string]{ result := NewConfigurable(
single: singleConfigurable[[]string]{ []ConfigurableCondition{{
conditions: []ConfigurableCondition{{ functionName: "release_variable",
functionName: "release_variable", args: []string{
args: []string{ "bar",
"bar",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "b",
}},
value: &[]string{"3", "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: &[]string{"1", "2"},
}},
}, },
}, }},
}, []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
}(),
}, },
}, },
} }
@ -1892,31 +1888,24 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase {
order: Append, order: Append,
dst: []interface{}{ dst: []interface{}{
&struct{ S Configurable[bool] }{ &struct{ S Configurable[bool] }{
S: Configurable[bool]{ S: NewConfigurable[bool]([]ConfigurableCondition{{
inner: &configurableInner[bool]{ functionName: "soong_config_variable",
single: singleConfigurable[bool]{ args: []string{
conditions: []ConfigurableCondition{{ "my_namespace",
functionName: "soong_config_variable", "foo",
args: []string{
"my_namespace",
"foo",
},
}},
cases: []ConfigurableCase[bool]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: BoolPtr(true),
}, {
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeDefault,
}},
value: BoolPtr(false),
}},
},
}, },
}, }}, []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 }{ src: &struct{ S *bool }{
@ -1924,38 +1913,30 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase {
}, },
out: []interface{}{ out: []interface{}{
&struct{ S Configurable[bool] }{ &struct{ S Configurable[bool] }{
S: Configurable[bool]{ S: func() Configurable[bool] {
inner: &configurableInner[bool]{ result := NewConfigurable[bool]([]ConfigurableCondition{{
single: singleConfigurable[bool]{ functionName: "soong_config_variable",
conditions: []ConfigurableCondition{{ args: []string{
functionName: "soong_config_variable", "my_namespace",
args: []string{ "foo",
"my_namespace",
"foo",
},
}},
cases: []ConfigurableCase[bool]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: BoolPtr(true),
}, {
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeDefault,
}},
value: BoolPtr(false),
}},
}, },
next: &configurableInner[bool]{ }},
single: singleConfigurable[bool]{ []ConfigurableCase[bool]{{
cases: []ConfigurableCase[bool]{{ patterns: []ConfigurablePattern{{
value: BoolPtr(true), typ: configurablePatternTypeString,
}}, stringValue: "a",
}, }},
}, value: &parser.Bool{Value: true},
}, }, {
}, patterns: []ConfigurablePattern{{
typ: configurablePatternTypeDefault,
}},
value: &parser.Bool{Value: false},
}},
)
result.AppendSimpleValue(true)
return result
}(),
}, },
}, },
}, },
@ -1964,31 +1945,26 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase {
order: Append, order: Append,
dst: []interface{}{ dst: []interface{}{
&struct{ S Configurable[bool] }{ &struct{ S Configurable[bool] }{
S: Configurable[bool]{ S: NewConfigurable[bool]([]ConfigurableCondition{{
inner: &configurableInner[bool]{ functionName: "soong_config_variable",
single: singleConfigurable[bool]{ args: []string{
conditions: []ConfigurableCondition{{ "my_namespace",
functionName: "soong_config_variable", "foo",
args: []string{
"my_namespace",
"foo",
},
}},
cases: []ConfigurableCase[bool]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: BoolPtr(true),
}, {
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeDefault,
}},
value: BoolPtr(false),
}},
},
}, },
}, }},
[]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 }{ src: &struct{ S bool }{
@ -1996,38 +1972,31 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase {
}, },
out: []interface{}{ out: []interface{}{
&struct{ S Configurable[bool] }{ &struct{ S Configurable[bool] }{
S: Configurable[bool]{ S: func() Configurable[bool] {
inner: &configurableInner[bool]{ result := NewConfigurable[bool](
single: singleConfigurable[bool]{ []ConfigurableCondition{{
conditions: []ConfigurableCondition{{ functionName: "soong_config_variable",
functionName: "soong_config_variable", args: []string{
args: []string{ "my_namespace",
"my_namespace", "foo",
"foo",
},
}},
cases: []ConfigurableCase[bool]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: BoolPtr(true),
}, {
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeDefault,
}},
value: BoolPtr(false),
}},
},
next: &configurableInner[bool]{
single: singleConfigurable[bool]{
cases: []ConfigurableCase[bool]{{
value: BoolPtr(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
}(),
}, },
}, },
}, },

View file

@ -158,7 +158,7 @@ func (ctx *unpackContext) buildPropertyMap(prefix string, properties []*parser.P
} }
ctx.propertyMap[name] = &packedProperty{property, false} ctx.propertyMap[name] = &packedProperty{property, false}
switch propValue := property.Value.Eval().(type) { switch propValue := property.Value.(type) {
case *parser.Map: case *parser.Map:
ctx.buildPropertyMap(name, propValue.Properties) ctx.buildPropertyMap(name, propValue.Properties)
case *parser.List: case *parser.List:
@ -313,7 +313,7 @@ func (ctx *unpackContext) unpackToStruct(namePrefix string, structValue reflect.
return return
} }
} else if isStruct(fieldValue.Type()) { } else if isStruct(fieldValue.Type()) {
if property.Value.Eval().Type() != parser.MapType { if property.Value.Type() != parser.MapType {
ctx.addError(&UnpackError{ ctx.addError(&UnpackError{
fmt.Errorf("can't assign %s value to map property %q", fmt.Errorf("can't assign %s value to map property %q",
property.Value.Type(), property.Name), property.Value.Type(), property.Name),
@ -354,15 +354,17 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa
}) })
return reflect.New(configurableType), false return reflect.New(configurableType), false
} }
var postProcessors [][]postProcessor[string]
result := Configurable[string]{ result := Configurable[string]{
propertyName: property.Name, propertyName: property.Name,
inner: &configurableInner[string]{ inner: &configurableInner[string]{
single: singleConfigurable[string]{ single: singleConfigurable[string]{
cases: []ConfigurableCase[string]{{ cases: []ConfigurableCase[string]{{
value: &v.Value, value: v,
}}, }},
}, },
}, },
postProcessors: &postProcessors,
} }
return reflect.ValueOf(&result), true return reflect.ValueOf(&result), true
case *parser.Bool: case *parser.Bool:
@ -374,15 +376,17 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa
}) })
return reflect.New(configurableType), false return reflect.New(configurableType), false
} }
var postProcessors [][]postProcessor[bool]
result := Configurable[bool]{ result := Configurable[bool]{
propertyName: property.Name, propertyName: property.Name,
inner: &configurableInner[bool]{ inner: &configurableInner[bool]{
single: singleConfigurable[bool]{ single: singleConfigurable[bool]{
cases: []ConfigurableCase[bool]{{ cases: []ConfigurableCase[bool]{{
value: &v.Value, value: v,
}}, }},
}, },
}, },
postProcessors: &postProcessors,
} }
return reflect.ValueOf(&result), true return reflect.ValueOf(&result), true
case *parser.List: case *parser.List:
@ -411,26 +415,22 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa
value[i] = exprUnpacked.Interface().(string) value[i] = exprUnpacked.Interface().(string)
} }
} }
var postProcessors [][]postProcessor[[]string]
result := Configurable[[]string]{ result := Configurable[[]string]{
propertyName: property.Name, propertyName: property.Name,
inner: &configurableInner[[]string]{ inner: &configurableInner[[]string]{
single: singleConfigurable[[]string]{ single: singleConfigurable[[]string]{
cases: []ConfigurableCase[[]string]{{ cases: []ConfigurableCase[[]string]{{
value: &value, value: v,
}}, }},
}, },
}, },
postProcessors: &postProcessors,
} }
return reflect.ValueOf(&result), true return reflect.ValueOf(&result), true
default: default:
panic("This should be unreachable because ConfigurableElements only accepts slices of strings") panic("This should be unreachable because ConfigurableElements only accepts slices of strings")
} }
case *parser.Operator:
property.Value = v.Value.Eval()
return ctx.unpackToConfigurable(propertyName, property, configurableType, configuredType)
case *parser.Variable:
property.Value = v.Value.Eval()
return ctx.unpackToConfigurable(propertyName, property, configurableType, configuredType)
case *parser.Select: case *parser.Select:
resultPtr := reflect.New(configurableType) resultPtr := reflect.New(configurableType)
result := resultPtr.Elem() result := resultPtr.Elem()
@ -448,19 +448,15 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa
configurableCaseType := configurableCaseType(configuredType) configurableCaseType := configurableCaseType(configuredType)
cases := reflect.MakeSlice(reflect.SliceOf(configurableCaseType), 0, len(v.Cases)) cases := reflect.MakeSlice(reflect.SliceOf(configurableCaseType), 0, len(v.Cases))
for i, c := range v.Cases { for _, c := range v.Cases {
p := &parser.Property{
Name: property.Name + "[" + strconv.Itoa(i) + "]",
NamePos: c.ColonPos,
Value: c.Value,
}
patterns := make([]ConfigurablePattern, len(c.Patterns)) patterns := make([]ConfigurablePattern, len(c.Patterns))
for i, pat := range c.Patterns { for i, pat := range c.Patterns {
switch pat := pat.(type) { switch pat := pat.Value.(type) {
case *parser.String: case *parser.String:
if pat.Value == "__soong_conditions_default__" { if pat.Value == "__soong_conditions_default__" {
patterns[i].typ = configurablePatternTypeDefault patterns[i].typ = configurablePatternTypeDefault
} else if pat.Value == "__soong_conditions_any__" {
patterns[i].typ = configurablePatternTypeAny
} else { } else {
patterns[i].typ = configurablePatternTypeString patterns[i].typ = configurablePatternTypeString
patterns[i].stringValue = pat.Value patterns[i].stringValue = pat.Value
@ -471,42 +467,15 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa
default: default:
panic("unimplemented") panic("unimplemented")
} }
} patterns[i].binding = pat.Binding.Name
var value reflect.Value
// Map the "unset" keyword to a nil pointer in the cases map
if _, ok := c.Value.(parser.UnsetProperty); ok {
value = reflect.Zero(reflect.PointerTo(configuredType))
} else {
var err error
switch configuredType.Kind() {
case reflect.String, reflect.Bool:
value, err = propertyToValue(reflect.PointerTo(configuredType), p)
if err != nil {
ctx.addError(&UnpackError{
err,
c.Value.Pos(),
})
return reflect.New(configurableType), false
}
case reflect.Slice:
if configuredType.Elem().Kind() != reflect.String {
panic("This should be unreachable because ConfigurableElements only accepts slices of strings")
}
value, ok = ctx.unpackToSlice(p.Name, p, reflect.PointerTo(configuredType))
if !ok {
return reflect.New(configurableType), false
}
default:
panic("This should be unreachable because ConfigurableElements only accepts strings, boools, or slices of strings")
}
} }
case_ := reflect.New(configurableCaseType) case_ := reflect.New(configurableCaseType)
case_.Interface().(configurableCaseReflection).initialize(patterns, value.Interface()) case_.Interface().(configurableCaseReflection).initialize(patterns, c.Value)
cases = reflect.Append(cases, case_.Elem()) cases = reflect.Append(cases, case_.Elem())
} }
resultPtr.Interface().(configurablePtrReflection).initialize( resultPtr.Interface().(configurablePtrReflection).initialize(
v.Scope,
property.Name, property.Name,
conditions, conditions,
cases.Interface(), cases.Interface(),
@ -537,7 +506,7 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa
// If the given property is a select, returns an error saying that you can't assign a select to // If the given property is a select, returns an error saying that you can't assign a select to
// a non-configurable property. Otherwise returns nil. // a non-configurable property. Otherwise returns nil.
func selectOnNonConfigurablePropertyError(property *parser.Property) error { func selectOnNonConfigurablePropertyError(property *parser.Property) error {
if _, ok := property.Value.Eval().(*parser.Select); !ok { if _, ok := property.Value.(*parser.Select); !ok {
return nil return nil
} }
@ -570,7 +539,7 @@ func (ctx *unpackContext) unpackToSlice(
// does. // does.
func (ctx *unpackContext) unpackToSliceInner( func (ctx *unpackContext) unpackToSliceInner(
sliceName string, property *parser.Property, sliceType reflect.Type) (reflect.Value, bool) { sliceName string, property *parser.Property, sliceType reflect.Type) (reflect.Value, bool) {
propValueAsList, ok := property.Value.Eval().(*parser.List) propValueAsList, ok := property.Value.(*parser.List)
if !ok { if !ok {
if err := selectOnNonConfigurablePropertyError(property); err != nil { if err := selectOnNonConfigurablePropertyError(property); err != nil {
ctx.addError(err) ctx.addError(err)
@ -590,33 +559,24 @@ func (ctx *unpackContext) unpackToSliceInner(
} }
// The function to construct an item value depends on the type of list elements. // The function to construct an item value depends on the type of list elements.
var getItemFunc func(*parser.Property, reflect.Type) (reflect.Value, bool) getItemFunc := func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
switch exprs[0].Type() { switch property.Value.(type) {
case parser.BoolType, parser.StringType, parser.Int64Type: case *parser.Bool, *parser.String, *parser.Int64:
getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
value, err := propertyToValue(t, property) value, err := propertyToValue(t, property)
if err != nil { if err != nil {
ctx.addError(err) ctx.addError(err)
return value, false return value, false
} }
return value, true return value, true
} case *parser.List:
case parser.ListType:
getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
return ctx.unpackToSlice(property.Name, property, t) return ctx.unpackToSlice(property.Name, property, t)
} case *parser.Map:
case parser.MapType:
getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
itemValue := reflect.New(t).Elem() itemValue := reflect.New(t).Elem()
ctx.unpackToStruct(property.Name, itemValue) ctx.unpackToStruct(property.Name, itemValue)
return itemValue, true return itemValue, true
default:
panic(fmt.Errorf("bizarre property expression type: %v, %#v", property.Value.Type(), property.Value))
} }
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} itemProperty := &parser.Property{NamePos: property.NamePos, ColonPos: property.ColonPos}
@ -657,7 +617,7 @@ func propertyToValue(typ reflect.Type, property *parser.Property) (reflect.Value
switch kind := baseType.Kind(); kind { switch kind := baseType.Kind(); kind {
case reflect.Bool: case reflect.Bool:
b, ok := property.Value.Eval().(*parser.Bool) b, ok := property.Value.(*parser.Bool)
if !ok { if !ok {
if err := selectOnNonConfigurablePropertyError(property); err != nil { if err := selectOnNonConfigurablePropertyError(property); err != nil {
return value, err return value, err
@ -672,7 +632,7 @@ func propertyToValue(typ reflect.Type, property *parser.Property) (reflect.Value
value = reflect.ValueOf(b.Value) value = reflect.ValueOf(b.Value)
case reflect.Int64: case reflect.Int64:
b, ok := property.Value.Eval().(*parser.Int64) b, ok := property.Value.(*parser.Int64)
if !ok { if !ok {
return value, &UnpackError{ return value, &UnpackError{
fmt.Errorf("can't assign %s value to int64 property %q", fmt.Errorf("can't assign %s value to int64 property %q",
@ -683,7 +643,7 @@ func propertyToValue(typ reflect.Type, property *parser.Property) (reflect.Value
value = reflect.ValueOf(b.Value) value = reflect.ValueOf(b.Value)
case reflect.String: case reflect.String:
s, ok := property.Value.Eval().(*parser.String) s, ok := property.Value.(*parser.String)
if !ok { if !ok {
if err := selectOnNonConfigurablePropertyError(property); err != nil { if err := selectOnNonConfigurablePropertyError(property); err != nil {
return value, err return value, err

View file

@ -18,6 +18,7 @@ import (
"bytes" "bytes"
"reflect" "reflect"
"testing" "testing"
"text/scanner"
"github.com/google/blueprint/parser" "github.com/google/blueprint/parser"
) )
@ -732,16 +733,21 @@ var validUnpackTestCases = []struct {
&struct { &struct {
Foo Configurable[string] Foo Configurable[string]
}{ }{
Foo: Configurable[string]{ Foo: newConfigurableWithPropertyName(
propertyName: "foo", "foo",
inner: &configurableInner[string]{ nil,
single: singleConfigurable[string]{ []ConfigurableCase[string]{{
cases: []ConfigurableCase[string]{{ value: &parser.String{
value: StringPtr("bar"), LiteralPos: scanner.Position{
}}, Offset: 17,
Line: 3,
Column: 10,
},
Value: "bar",
}, },
}, }},
}, false,
),
}, },
}, },
}, },
@ -756,16 +762,22 @@ var validUnpackTestCases = []struct {
&struct { &struct {
Foo Configurable[bool] Foo Configurable[bool]
}{ }{
Foo: Configurable[bool]{ Foo: newConfigurableWithPropertyName(
propertyName: "foo", "foo",
inner: &configurableInner[bool]{ nil,
single: singleConfigurable[bool]{ []ConfigurableCase[bool]{{
cases: []ConfigurableCase[bool]{{ value: &parser.Bool{
value: BoolPtr(true), LiteralPos: scanner.Position{
}}, Offset: 17,
Line: 3,
Column: 10,
},
Value: true,
Token: "true",
}, },
}, }},
}, false,
),
}, },
}, },
}, },
@ -780,16 +792,43 @@ var validUnpackTestCases = []struct {
&struct { &struct {
Foo Configurable[[]string] Foo Configurable[[]string]
}{ }{
Foo: Configurable[[]string]{ Foo: newConfigurableWithPropertyName(
propertyName: "foo", "foo",
inner: &configurableInner[[]string]{ nil,
single: singleConfigurable[[]string]{ []ConfigurableCase[[]string]{{
cases: []ConfigurableCase[[]string]{{ value: &parser.List{
value: &[]string{"a", "b"}, 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: 11,
},
Value: "a",
},
&parser.String{
LiteralPos: scanner.Position{
Offset: 23,
Line: 3,
Column: 16,
},
Value: "b",
},
},
}, },
}, }},
}, false,
),
}, },
}, },
}, },
@ -808,42 +847,60 @@ var validUnpackTestCases = []struct {
&struct { &struct {
Foo Configurable[string] Foo Configurable[string]
}{ }{
Foo: Configurable[string]{ Foo: newConfigurableWithPropertyName(
propertyName: "foo", "foo",
inner: &configurableInner[string]{ []ConfigurableCondition{{
single: singleConfigurable[string]{ functionName: "soong_config_variable",
conditions: []ConfigurableCondition{{ args: []string{
functionName: "soong_config_variable", "my_namespace",
args: []string{ "my_variable",
"my_namespace", },
"my_variable", }},
}, []ConfigurableCase[string]{
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}}, }},
cases: []ConfigurableCase[string]{ value: &parser.String{
{ LiteralPos: scanner.Position{
patterns: []ConfigurablePattern{{ Offset: 90,
typ: configurablePatternTypeString, Line: 4,
stringValue: "a", Column: 11,
}},
value: StringPtr("a2"),
}, },
{ Value: "a2",
patterns: []ConfigurablePattern{{ },
typ: configurablePatternTypeString, },
stringValue: "b", {
}}, patterns: []ConfigurablePattern{{
value: StringPtr("b2"), typ: configurablePatternTypeString,
stringValue: "b",
}},
value: &parser.String{
LiteralPos: scanner.Position{
Offset: 106,
Line: 5,
Column: 11,
}, },
{ Value: "b2",
patterns: []ConfigurablePattern{{ },
typ: configurablePatternTypeDefault, },
}}, {
value: StringPtr("c2"), patterns: []ConfigurablePattern{{
typ: configurablePatternTypeDefault,
}},
value: &parser.String{
LiteralPos: scanner.Position{
Offset: 126,
Line: 6,
Column: 15,
}, },
Value: "c2",
}, },
}, },
}, },
}, true,
),
}, },
}, },
}, },
@ -866,75 +923,117 @@ var validUnpackTestCases = []struct {
&struct { &struct {
Foo Configurable[string] Foo Configurable[string]
}{ }{
Foo: Configurable[string]{ Foo: func() Configurable[string] {
propertyName: "foo", result := newConfigurableWithPropertyName(
inner: &configurableInner[string]{ "foo",
single: singleConfigurable[string]{ []ConfigurableCondition{{
conditions: []ConfigurableCondition{{ functionName: "soong_config_variable",
functionName: "soong_config_variable", args: []string{
args: []string{ "my_namespace",
"my_namespace", "my_variable",
"my_variable",
},
}},
cases: []ConfigurableCase[string]{
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: StringPtr("a2"),
},
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "b",
}},
value: StringPtr("b2"),
},
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeDefault,
}},
value: StringPtr("c2"),
},
}, },
}, }},
next: &configurableInner[string]{ []ConfigurableCase[string]{
single: singleConfigurable[string]{ {
conditions: []ConfigurableCondition{{ patterns: []ConfigurablePattern{{
functionName: "soong_config_variable", typ: configurablePatternTypeString,
args: []string{ stringValue: "a",
"my_namespace",
"my_2nd_variable",
},
}}, }},
cases: []ConfigurableCase[string]{ value: &parser.String{
{ LiteralPos: scanner.Position{
patterns: []ConfigurablePattern{{ Offset: 90,
typ: configurablePatternTypeString, Line: 4,
stringValue: "d", Column: 11,
}},
value: StringPtr("d2"),
}, },
{ Value: "a2",
patterns: []ConfigurablePattern{{ },
typ: configurablePatternTypeString, },
stringValue: "e", {
}}, patterns: []ConfigurablePattern{{
value: StringPtr("e2"), typ: configurablePatternTypeString,
stringValue: "b",
}},
value: &parser.String{
LiteralPos: scanner.Position{
Offset: 106,
Line: 5,
Column: 11,
}, },
{ Value: "b2",
patterns: []ConfigurablePattern{{ },
typ: configurablePatternTypeDefault, },
}}, {
value: StringPtr("f2"), 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
}(),
}, },
}, },
}, },
@ -953,26 +1052,37 @@ var validUnpackTestCases = []struct {
Foo Configurable[string] Foo Configurable[string]
Bar Configurable[bool] Bar Configurable[bool]
}{ }{
Foo: Configurable[string]{ Foo: newConfigurableWithPropertyName(
propertyName: "foo", "foo",
inner: &configurableInner[string]{ nil,
single: singleConfigurable[string]{ []ConfigurableCase[string]{{
cases: []ConfigurableCase[string]{{ value: &parser.String{
value: StringPtr("asdf"), LiteralPos: scanner.Position{
}}, Offset: 25,
Line: 2,
Column: 25,
},
Value: "asdf",
}, },
}, }},
}, false,
Bar: Configurable[bool]{ ),
propertyName: "bar", Bar: newConfigurableWithPropertyName(
inner: &configurableInner[bool]{ "bar",
single: singleConfigurable[bool]{ nil,
cases: []ConfigurableCase[bool]{{ []ConfigurableCase[bool]{{
value: BoolPtr(true), value: &parser.Bool{
}}, LiteralPos: scanner.Position{
Offset: 54,
Line: 3,
Column: 23,
},
Value: true,
Token: "true",
}, },
}, }},
}, false,
),
}, },
}, },
}, },