Compare commits
4 commits
b7e4d26faf
...
0a002f5985
Author | SHA1 | Date | |
---|---|---|---|
|
0a002f5985 | ||
|
c472e38ec1 | ||
|
738bb54ded | ||
|
1e62c68bfe |
18 changed files with 1697 additions and 1380 deletions
|
@ -133,6 +133,7 @@ bootstrap_go_package {
|
|||
],
|
||||
testSrcs: [
|
||||
"proptools/clone_test.go",
|
||||
"proptools/configurable_test.go",
|
||||
"proptools/escape_test.go",
|
||||
"proptools/extend_test.go",
|
||||
"proptools/filter_test.go",
|
||||
|
|
|
@ -66,7 +66,7 @@ func processReader(filename string, in io.Reader, out io.Writer) error {
|
|||
|
||||
r := bytes.NewBuffer(src)
|
||||
|
||||
file, errs := parser.Parse(filename, r, parser.NewScope(nil))
|
||||
file, errs := parser.Parse(filename, r)
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
|
|
|
@ -85,7 +85,7 @@ func processFile(filename string, in io.Reader, out io.Writer) error {
|
|||
return err
|
||||
}
|
||||
r := bytes.NewBuffer(src)
|
||||
file, errs := parser.Parse(filename, r, parser.NewScope(nil))
|
||||
file, errs := parser.Parse(filename, r)
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
|
@ -131,11 +131,13 @@ func findModules(file *parser.File) (modified bool, errs []error) {
|
|||
for _, def := range file.Defs {
|
||||
if module, ok := def.(*parser.Module); ok {
|
||||
for _, prop := range module.Properties {
|
||||
if prop.Name == "name" && prop.Value.Type() == parser.StringType && targetedModule(prop.Value.Eval().(*parser.String).Value) {
|
||||
for _, p := range targetedProperties.properties {
|
||||
m, newErrs := processModuleProperty(module, prop.Name, file, p)
|
||||
errs = append(errs, newErrs...)
|
||||
modified = modified || m
|
||||
if prop.Name == "name" {
|
||||
if stringValue, ok := prop.Value.(*parser.String); ok && targetedModule(stringValue.Value) {
|
||||
for _, p := range targetedProperties.properties {
|
||||
m, newErrs := processModuleProperty(module, prop.Name, file, p)
|
||||
errs = append(errs, newErrs...)
|
||||
modified = modified || m
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -194,7 +196,7 @@ func getOrCreateRecursiveProperty(module *parser.Module, name string, prefixes [
|
|||
m := &module.Map
|
||||
for i, prefix := range prefixes {
|
||||
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
|
||||
} else {
|
||||
// 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 list, ok := value.Eval().(*parser.List); ok {
|
||||
if list, ok := value.(*parser.List); ok {
|
||||
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
|
||||
replacementValue := (*replaceProperty).oldNameToNewName[oldVal]
|
||||
if replacementValue != "" {
|
||||
|
|
28
context.go
28
context.go
|
@ -1221,9 +1221,9 @@ func (c *Context) parseOne(rootDir, filename string, reader io.Reader,
|
|||
return nil, nil, []error{err}
|
||||
}
|
||||
|
||||
scope.Remove("subdirs")
|
||||
scope.Remove("optional_subdirs")
|
||||
scope.Remove("build")
|
||||
scope.DontInherit("subdirs")
|
||||
scope.DontInherit("optional_subdirs")
|
||||
scope.DontInherit("build")
|
||||
file, errs = parser.ParseAndEval(filename, reader, scope)
|
||||
if len(errs) > 0 {
|
||||
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) {
|
||||
if assignment, local := scope.Get(v); assignment == nil || !local {
|
||||
if assignment := scope.GetLocal(v); assignment == nil {
|
||||
return nil, scanner.Position{}, nil
|
||||
} else {
|
||||
switch value := assignment.Value.Eval().(type) {
|
||||
switch value := assignment.Value.(type) {
|
||||
case *parser.List:
|
||||
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
|
||||
// property values. Any values stored in the module object that are not stored in properties
|
||||
// structs will be lost.
|
||||
|
|
|
@ -1392,8 +1392,7 @@ func runAndRemoveLoadHooks(ctx *Context, config interface{}, module *moduleInfo,
|
|||
//
|
||||
// The filename is only used for reporting errors.
|
||||
func CheckBlueprintSyntax(moduleFactories map[string]ModuleFactory, filename string, contents string) []error {
|
||||
scope := parser.NewScope(nil)
|
||||
file, errs := parser.Parse(filename, strings.NewReader(contents), scope)
|
||||
file, errs := parser.Parse(filename, strings.NewReader(contents))
|
||||
if len(errs) != 0 {
|
||||
return errs
|
||||
}
|
||||
|
|
416
parser/ast.go
416
parser/ast.go
|
@ -16,6 +16,7 @@ package parser
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
)
|
||||
|
@ -40,14 +41,13 @@ type Assignment struct {
|
|||
Name string
|
||||
NamePos scanner.Position
|
||||
Value Expression
|
||||
OrigValue Expression
|
||||
EqualsPos scanner.Position
|
||||
Assigner string
|
||||
Referenced bool
|
||||
}
|
||||
|
||||
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 }
|
||||
|
@ -131,6 +131,10 @@ func (p *Property) String() string {
|
|||
func (p *Property) Pos() scanner.Position { return p.NamePos }
|
||||
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
|
||||
// Map, a List, an Operator that combines two expressions of the same type, or a Variable that
|
||||
// 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() Expression
|
||||
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
|
||||
// Eval returns an expression that is fully evaluated to a simple type (List, Map, String, or
|
||||
// Bool). It will return the same object for every call to Eval().
|
||||
Eval() Expression
|
||||
// Eval returns an expression that is fully evaluated to a simple type (List, Map, String,
|
||||
// Bool, or Select). It will return the origional expression if possible, or allocate a
|
||||
// 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.
|
||||
|
@ -157,9 +174,6 @@ func ExpressionsAreSame(a Expression, b Expression) (equal bool, err error) {
|
|||
// TODO(jeffrygaston) once positions are removed from Expression structs,
|
||||
// remove this function and have callers use reflect.DeepEqual(a, b)
|
||||
func hackyExpressionsAreSame(a Expression, b Expression) (equal bool, err error) {
|
||||
if a.Type() != b.Type() {
|
||||
return false, nil
|
||||
}
|
||||
left, err := hackyFingerprint(a)
|
||||
if err != 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) {
|
||||
assignment := &Assignment{"a", noPos, expression, expression, noPos, "=", false}
|
||||
assignment := &Assignment{"a", noPos, expression, noPos, "=", false}
|
||||
module := &File{}
|
||||
module.Defs = append(module.Defs, assignment)
|
||||
p := newPrinter(module)
|
||||
|
@ -183,17 +197,19 @@ func hackyFingerprint(expression Expression) (fingerprint []byte, err error) {
|
|||
type Type int
|
||||
|
||||
const (
|
||||
BoolType Type = iota + 1
|
||||
UnknownType Type = iota
|
||||
BoolType
|
||||
StringType
|
||||
Int64Type
|
||||
ListType
|
||||
MapType
|
||||
NotEvaluatedType
|
||||
UnsetType
|
||||
)
|
||||
|
||||
func (t Type) String() string {
|
||||
switch t {
|
||||
case UnknownType:
|
||||
return "unknown"
|
||||
case BoolType:
|
||||
return "bool"
|
||||
case StringType:
|
||||
|
@ -204,12 +220,10 @@ func (t Type) String() string {
|
|||
return "list"
|
||||
case MapType:
|
||||
return "map"
|
||||
case NotEvaluatedType:
|
||||
return "notevaluated"
|
||||
case UnsetType:
|
||||
return "unset"
|
||||
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
|
||||
Operator rune
|
||||
OperatorPos scanner.Position
|
||||
Value Expression
|
||||
}
|
||||
|
||||
func (x *Operator) Copy() Expression {
|
||||
|
@ -227,26 +240,142 @@ func (x *Operator) Copy() Expression {
|
|||
return &ret
|
||||
}
|
||||
|
||||
func (x *Operator) Eval() Expression {
|
||||
return x.Value.Eval()
|
||||
func (x *Operator) Type() Type {
|
||||
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 {
|
||||
return x.Args[0].Type()
|
||||
func (x *Operator) Eval(scope *Scope) (Expression, error) {
|
||||
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) End() scanner.Position { return x.Args[1].End() }
|
||||
|
||||
func (x *Operator) String() string {
|
||||
return fmt.Sprintf("(%s %c %s = %s)@%s", x.Args[0].String(), x.Operator, x.Args[1].String(),
|
||||
x.Value, x.OperatorPos)
|
||||
return fmt.Sprintf("(%s %c %s)@%s", x.Args[0].String(), x.Operator, x.Args[1].String(),
|
||||
x.OperatorPos)
|
||||
}
|
||||
|
||||
type Variable struct {
|
||||
Name string
|
||||
NamePos scanner.Position
|
||||
Value Expression
|
||||
Type_ Type
|
||||
}
|
||||
|
||||
func (x *Variable) Pos() scanner.Position { return x.NamePos }
|
||||
|
@ -257,15 +386,33 @@ func (x *Variable) Copy() Expression {
|
|||
return &ret
|
||||
}
|
||||
|
||||
func (x *Variable) Eval() Expression {
|
||||
return x.Value.Eval()
|
||||
func (x *Variable) Eval(scope *Scope) (Expression, error) {
|
||||
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 {
|
||||
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 {
|
||||
LBracePos scanner.Position
|
||||
|
@ -285,8 +432,36 @@ func (x *Map) Copy() Expression {
|
|||
return &ret
|
||||
}
|
||||
|
||||
func (x *Map) Eval() Expression {
|
||||
return x
|
||||
func (x *Map) Eval(scope *Scope) (Expression, error) {
|
||||
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 {
|
||||
|
@ -379,8 +554,35 @@ func (x *List) Copy() Expression {
|
|||
return &ret
|
||||
}
|
||||
|
||||
func (x *List) Eval() Expression {
|
||||
return x
|
||||
func (x *List) Eval(scope *Scope) (Expression, error) {
|
||||
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 {
|
||||
|
@ -407,8 +609,29 @@ func (x *String) Copy() Expression {
|
|||
return &ret
|
||||
}
|
||||
|
||||
func (x *String) Eval() Expression {
|
||||
return x
|
||||
func (x *String) Eval(scope *Scope) (Expression, error) {
|
||||
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 {
|
||||
|
@ -433,8 +656,15 @@ func (x *Int64) Copy() Expression {
|
|||
return &ret
|
||||
}
|
||||
|
||||
func (x *Int64) Eval() Expression {
|
||||
return x
|
||||
func (x *Int64) Eval(scope *Scope) (Expression, error) {
|
||||
return x, nil
|
||||
}
|
||||
|
||||
func (x *Int64) PrintfInto(value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Int64) MarkReferencedVariables(scope *Scope) {
|
||||
}
|
||||
|
||||
func (x *Int64) String() string {
|
||||
|
@ -459,8 +689,15 @@ func (x *Bool) Copy() Expression {
|
|||
return &ret
|
||||
}
|
||||
|
||||
func (x *Bool) Eval() Expression {
|
||||
return x
|
||||
func (x *Bool) Eval(scope *Scope) (Expression, error) {
|
||||
return x, nil
|
||||
}
|
||||
|
||||
func (x *Bool) PrintfInto(value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Bool) MarkReferencedVariables(scope *Scope) {
|
||||
}
|
||||
|
||||
func (x *Bool) String() string {
|
||||
|
@ -542,29 +779,6 @@ func (c Comment) Text() string {
|
|||
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 {
|
||||
pos.Offset += n
|
||||
pos.Column += n
|
||||
|
@ -609,13 +823,13 @@ func (c *ConfigurableCondition) String() string {
|
|||
}
|
||||
|
||||
type Select struct {
|
||||
KeywordPos scanner.Position // the keyword "select"
|
||||
Conditions []ConfigurableCondition
|
||||
LBracePos scanner.Position
|
||||
RBracePos scanner.Position
|
||||
Cases []*SelectCase // the case statements
|
||||
Append Expression
|
||||
ExpressionType Type
|
||||
Scope *Scope // scope used to evaluate the body of the select later on
|
||||
KeywordPos scanner.Position // the keyword "select"
|
||||
Conditions []ConfigurableCondition
|
||||
LBracePos scanner.Position
|
||||
RBracePos scanner.Position
|
||||
Cases []*SelectCase // the case statements
|
||||
Append Expression
|
||||
}
|
||||
|
||||
func (s *Select) Pos() scanner.Position { return s.KeywordPos }
|
||||
|
@ -633,8 +847,24 @@ func (s *Select) Copy() Expression {
|
|||
return &ret
|
||||
}
|
||||
|
||||
func (s *Select) Eval() Expression {
|
||||
return s
|
||||
func (s *Select) Eval(scope *Scope) (Expression, error) {
|
||||
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 {
|
||||
|
@ -642,18 +872,35 @@ func (s *Select) String() string {
|
|||
}
|
||||
|
||||
func (s *Select) Type() Type {
|
||||
if s.ExpressionType == UnsetType && s.Append != nil {
|
||||
return s.Append.Type()
|
||||
if len(s.Cases) == 0 {
|
||||
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 {
|
||||
Patterns []Expression
|
||||
Patterns []SelectPattern
|
||||
ColonPos scanner.Position
|
||||
Value Expression
|
||||
}
|
||||
|
||||
func (x *SelectCase) MarkReferencedVariables(scope *Scope) {
|
||||
x.Value.MarkReferencedVariables(scope)
|
||||
}
|
||||
|
||||
func (c *SelectCase) Copy() *SelectCase {
|
||||
ret := *c
|
||||
ret.Value = c.Value.Copy()
|
||||
|
@ -681,21 +928,28 @@ type UnsetProperty struct {
|
|||
Position scanner.Position
|
||||
}
|
||||
|
||||
func (n UnsetProperty) Copy() Expression {
|
||||
return UnsetProperty{Position: n.Position}
|
||||
func (n *UnsetProperty) Copy() Expression {
|
||||
return &UnsetProperty{Position: n.Position}
|
||||
}
|
||||
|
||||
func (n UnsetProperty) String() string {
|
||||
func (n *UnsetProperty) String() string {
|
||||
return "unset"
|
||||
}
|
||||
|
||||
func (n UnsetProperty) Type() Type {
|
||||
func (n *UnsetProperty) Type() Type {
|
||||
return UnsetType
|
||||
}
|
||||
|
||||
func (n UnsetProperty) Eval() Expression {
|
||||
return UnsetProperty{Position: n.Position}
|
||||
func (n *UnsetProperty) Eval(scope *Scope) (Expression, error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (n UnsetProperty) Pos() scanner.Position { return n.Position }
|
||||
func (n UnsetProperty) End() scanner.Position { return n.Position }
|
||||
func (x *UnsetProperty) PrintfInto(value string) error {
|
||||
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 }
|
||||
|
|
|
@ -23,13 +23,11 @@ import (
|
|||
|
||||
func AddStringToList(list *List, s string) (modified bool) {
|
||||
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 {
|
||||
// string already exists
|
||||
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) {
|
||||
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 {
|
||||
list.Values = append(list.Values[:i], list.Values[i+1:]...)
|
||||
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) {
|
||||
modified := false
|
||||
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] != "" {
|
||||
pos := list.Values[i].Pos()
|
||||
list.Values[i] = &String{
|
||||
|
@ -69,6 +62,8 @@ func ReplaceStringsInList(list *List, replacements map[string]string) (replaced
|
|||
Value: replacements[sv.Value],
|
||||
}
|
||||
modified = true
|
||||
} else if !ok {
|
||||
panic(fmt.Errorf("expected string in list, got %s", v.Type()))
|
||||
}
|
||||
}
|
||||
return modified
|
||||
|
|
465
parser/parser.go
465
parser/parser.go
|
@ -29,6 +29,7 @@ var errTooManyErrors = errors.New("too many errors")
|
|||
const maxErrors = 1
|
||||
|
||||
const default_select_branch_name = "__soong_conditions_default__"
|
||||
const any_select_branch_name = "__soong_conditions_any__"
|
||||
|
||||
type ParseError struct {
|
||||
Err error
|
||||
|
@ -45,22 +46,6 @@ type File struct {
|
|||
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) {
|
||||
defer func() {
|
||||
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) {
|
||||
p := newParser(r, scope)
|
||||
p.eval = true
|
||||
p.scanner.Filename = filename
|
||||
file, errs = Parse(filename, r)
|
||||
if len(errs) > 0 {
|
||||
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) {
|
||||
p := newParser(r, scope)
|
||||
func Parse(filename string, r io.Reader) (file *File, errs []error) {
|
||||
p := newParser(r)
|
||||
p.scanner.Filename = filename
|
||||
|
||||
return parse(p)
|
||||
}
|
||||
|
||||
func ParseExpression(r io.Reader) (value Expression, errs []error) {
|
||||
p := newParser(r, NewScope(nil))
|
||||
p := newParser(r)
|
||||
p.next()
|
||||
value = p.parseExpression()
|
||||
p.accept(scanner.EOF)
|
||||
|
@ -114,14 +131,11 @@ type parser struct {
|
|||
scanner scanner.Scanner
|
||||
tok rune
|
||||
errors []error
|
||||
scope *Scope
|
||||
comments []*CommentGroup
|
||||
eval bool
|
||||
}
|
||||
|
||||
func newParser(r io.Reader, scope *Scope) *parser {
|
||||
func newParser(r io.Reader) *parser {
|
||||
p := &parser{}
|
||||
p.scope = scope
|
||||
p.scanner.Init(r)
|
||||
p.scanner.Error = func(sc *scanner.Scanner, msg string) {
|
||||
p.errorf(msg)
|
||||
|
@ -234,34 +248,9 @@ func (p *parser) parseAssignment(name string, namePos scanner.Position,
|
|||
assignment.Name = name
|
||||
assignment.NamePos = namePos
|
||||
assignment.Value = value
|
||||
assignment.OrigValue = value
|
||||
assignment.EqualsPos = pos
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -297,13 +286,7 @@ func (p *parser) parseModule(typ string, typPos scanner.Position) *Module {
|
|||
|
||||
func (p *parser) parsePropertyList(isModule, compat bool) (properties []*Property) {
|
||||
for p.tok == scanner.Ident {
|
||||
property := 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)
|
||||
}
|
||||
properties = append(properties, p.parseProperty(isModule, compat))
|
||||
|
||||
if p.tok != ',' {
|
||||
// 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 {
|
||||
operator := p.tok
|
||||
pos := p.scanner.Position
|
||||
|
@ -479,14 +353,11 @@ func (p *parser) parseOperator(value1 Expression) Expression {
|
|||
|
||||
value2 := p.parseExpression()
|
||||
|
||||
value, err := p.evaluateOperator(value1, value2, operator, pos)
|
||||
if err != nil {
|
||||
p.error(err)
|
||||
return nil
|
||||
return &Operator{
|
||||
Args: [2]Expression{value1, value2},
|
||||
Operator: operator,
|
||||
OperatorPos: pos,
|
||||
}
|
||||
|
||||
return value
|
||||
|
||||
}
|
||||
|
||||
func (p *parser) parseValue() (value Expression) {
|
||||
|
@ -535,22 +406,9 @@ func (p *parser) parseVariable() Expression {
|
|||
var value Expression
|
||||
|
||||
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{
|
||||
Name: text,
|
||||
NamePos: p.scanner.Position,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
p.accept(scanner.Ident)
|
||||
|
@ -645,44 +503,72 @@ func (p *parser) parseSelect() Expression {
|
|||
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 {
|
||||
case scanner.Ident:
|
||||
switch p.scanner.TokenText() {
|
||||
case "default":
|
||||
case "any":
|
||||
result.Value = &String{
|
||||
LiteralPos: p.scanner.Position,
|
||||
Value: any_select_branch_name,
|
||||
}
|
||||
p.next()
|
||||
return &String{
|
||||
if binding, exists := maybeParseBinding(); exists {
|
||||
result.Binding = binding
|
||||
}
|
||||
return result
|
||||
case "default":
|
||||
result.Value = &String{
|
||||
LiteralPos: p.scanner.Position,
|
||||
Value: default_select_branch_name,
|
||||
}
|
||||
case "true":
|
||||
p.next()
|
||||
return &Bool{
|
||||
return result
|
||||
case "true":
|
||||
result.Value = &Bool{
|
||||
LiteralPos: p.scanner.Position,
|
||||
Value: true,
|
||||
}
|
||||
case "false":
|
||||
p.next()
|
||||
return &Bool{
|
||||
return result
|
||||
case "false":
|
||||
result.Value = &Bool{
|
||||
LiteralPos: p.scanner.Position,
|
||||
Value: false,
|
||||
}
|
||||
p.next()
|
||||
return result
|
||||
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:
|
||||
if s := p.parseStringValue(); s != nil {
|
||||
if strings.HasPrefix(s.Value, "__soong") {
|
||||
p.errorf("select branch conditions starting with __soong are reserved for internal use")
|
||||
return nil
|
||||
p.errorf("select branch patterns starting with __soong are reserved for internal use")
|
||||
return result
|
||||
}
|
||||
return s
|
||||
result.Value = s
|
||||
return result
|
||||
}
|
||||
fallthrough
|
||||
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
|
||||
|
@ -694,11 +580,7 @@ func (p *parser) parseSelect() Expression {
|
|||
return nil
|
||||
}
|
||||
for i := 0; i < len(conditions); i++ {
|
||||
if p := parseOnePattern(); p != nil {
|
||||
c.Patterns = append(c.Patterns, p)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
c.Patterns = append(c.Patterns, parseOnePattern())
|
||||
if i < len(conditions)-1 {
|
||||
if !p.accept(',') {
|
||||
return nil
|
||||
|
@ -712,18 +594,14 @@ func (p *parser) parseSelect() Expression {
|
|||
return nil
|
||||
}
|
||||
} else {
|
||||
if p := parseOnePattern(); p != nil {
|
||||
c.Patterns = append(c.Patterns, p)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
c.Patterns = append(c.Patterns, parseOnePattern())
|
||||
}
|
||||
c.ColonPos = p.scanner.Position
|
||||
if !p.accept(':') {
|
||||
return nil
|
||||
}
|
||||
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)
|
||||
} else {
|
||||
hasNonUnsetValue = true
|
||||
|
@ -742,16 +620,17 @@ func (p *parser) parseSelect() Expression {
|
|||
return nil
|
||||
}
|
||||
|
||||
patternsEqual := func(a, b Expression) bool {
|
||||
switch a2 := a.(type) {
|
||||
patternsEqual := func(a, b SelectPattern) bool {
|
||||
// We can ignore the bindings, they don't affect which pattern is matched
|
||||
switch a2 := a.Value.(type) {
|
||||
case *String:
|
||||
if b2, ok := b.(*String); ok {
|
||||
if b2, ok := b.Value.(*String); ok {
|
||||
return a2.Value == b2.Value
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case *Bool:
|
||||
if b2, ok := b.(*Bool); ok {
|
||||
if b2, ok := b.Value.(*Bool); ok {
|
||||
return a2.Value == b2.Value
|
||||
} else {
|
||||
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) {
|
||||
return false
|
||||
}
|
||||
|
@ -775,18 +654,29 @@ func (p *parser) parseSelect() Expression {
|
|||
}
|
||||
|
||||
for i, c := range result.Cases {
|
||||
// Check for duplicates
|
||||
// Check for duplicate patterns across different branches
|
||||
for _, d := range result.Cases[i+1:] {
|
||||
if patternListsEqual(c.Patterns, d.Patterns) {
|
||||
p.errorf("Found duplicate select patterns: %v", c.Patterns)
|
||||
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
|
||||
if i < len(result.Cases)-1 {
|
||||
isAllDefault := true
|
||||
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
|
||||
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
|
||||
if !p.accept('}') {
|
||||
return nil
|
||||
|
@ -913,79 +788,107 @@ func (p *parser) parseMapValue() *Map {
|
|||
}
|
||||
|
||||
type Scope struct {
|
||||
vars map[string]*Assignment
|
||||
inheritedVars map[string]*Assignment
|
||||
vars map[string]*Assignment
|
||||
preventInheriting map[string]bool
|
||||
parentScope *Scope
|
||||
}
|
||||
|
||||
func NewScope(s *Scope) *Scope {
|
||||
newScope := &Scope{
|
||||
vars: make(map[string]*Assignment),
|
||||
inheritedVars: make(map[string]*Assignment),
|
||||
return &Scope{
|
||||
vars: 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 {
|
||||
if old, ok := s.vars[assignment.Name]; ok {
|
||||
return fmt.Errorf("variable already set, previous assignment: %s", old)
|
||||
func (s *Scope) HandleAssignment(assignment *Assignment) error {
|
||||
switch assignment.Assigner {
|
||||
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
|
||||
}
|
||||
|
||||
func (s *Scope) Remove(name string) {
|
||||
delete(s.vars, name)
|
||||
delete(s.inheritedVars, name)
|
||||
func (s *Scope) Get(name string) *Assignment {
|
||||
if s == nil {
|
||||
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 {
|
||||
return a, true
|
||||
return a
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if a, ok := s.inheritedVars[name]; ok {
|
||||
return a, false
|
||||
}
|
||||
|
||||
return nil, false
|
||||
// DontInherit prevents this scope from inheriting the given variable from its
|
||||
// parent scope.
|
||||
func (s *Scope) DontInherit(name string) {
|
||||
s.preventInheriting[name] = true
|
||||
}
|
||||
|
||||
func (s *Scope) String() string {
|
||||
vars := []string{}
|
||||
var sb strings.Builder
|
||||
s.stringInner(&sb)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
for k := range s.vars {
|
||||
vars = append(vars, k)
|
||||
func (s *Scope) stringInner(sb *strings.Builder) {
|
||||
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)
|
||||
}
|
||||
|
||||
sort.Strings(vars)
|
||||
|
||||
ret := []string{}
|
||||
for _, v := range vars {
|
||||
if assignment, ok := s.vars[v]; ok {
|
||||
ret = append(ret, assignment.String())
|
||||
} else {
|
||||
ret = append(ret, s.inheritedVars[v].String())
|
||||
}
|
||||
sb.WriteString(s.vars[v].String())
|
||||
sb.WriteRune('\n')
|
||||
}
|
||||
|
||||
return strings.Join(ret, "\n")
|
||||
s.parentScope.stringInner(sb)
|
||||
}
|
||||
|
|
|
@ -565,12 +565,7 @@ var validParseTestCases = []struct {
|
|||
LiteralPos: mkpos(9, 2, 9),
|
||||
Value: "stuff",
|
||||
},
|
||||
OrigValue: &String{
|
||||
LiteralPos: mkpos(9, 2, 9),
|
||||
Value: "stuff",
|
||||
},
|
||||
Assigner: "=",
|
||||
Referenced: true,
|
||||
Assigner: "=",
|
||||
},
|
||||
&Assignment{
|
||||
Name: "bar",
|
||||
|
@ -579,21 +574,8 @@ var validParseTestCases = []struct {
|
|||
Value: &Variable{
|
||||
Name: "foo",
|
||||
NamePos: mkpos(25, 3, 9),
|
||||
Value: &String{
|
||||
LiteralPos: mkpos(9, 2, 9),
|
||||
Value: "stuff",
|
||||
},
|
||||
},
|
||||
OrigValue: &Variable{
|
||||
Name: "foo",
|
||||
NamePos: mkpos(25, 3, 9),
|
||||
Value: &String{
|
||||
LiteralPos: mkpos(9, 2, 9),
|
||||
Value: "stuff",
|
||||
},
|
||||
},
|
||||
Assigner: "=",
|
||||
Referenced: true,
|
||||
Assigner: "=",
|
||||
},
|
||||
&Assignment{
|
||||
Name: "baz",
|
||||
|
@ -602,155 +584,26 @@ var validParseTestCases = []struct {
|
|||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
OrigValue: &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: "=",
|
||||
Referenced: true,
|
||||
Assigner: "=",
|
||||
},
|
||||
&Assignment{
|
||||
Name: "boo",
|
||||
NamePos: mkpos(49, 5, 3),
|
||||
EqualsPos: mkpos(53, 5, 7),
|
||||
Value: &Operator{
|
||||
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{
|
||||
Value: &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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Assigner: "=",
|
||||
},
|
||||
|
@ -761,18 +614,6 @@ var validParseTestCases = []struct {
|
|||
Value: &Variable{
|
||||
Name: "foo",
|
||||
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: "+=",
|
||||
},
|
||||
|
@ -791,10 +632,6 @@ var validParseTestCases = []struct {
|
|||
Value: &Operator{
|
||||
OperatorPos: mkpos(12, 2, 12),
|
||||
Operator: '+',
|
||||
Value: &Int64{
|
||||
LiteralPos: mkpos(9, 2, 9),
|
||||
Value: -3,
|
||||
},
|
||||
Args: [2]Expression{
|
||||
&Int64{
|
||||
LiteralPos: mkpos(9, 2, 9),
|
||||
|
@ -804,10 +641,6 @@ var validParseTestCases = []struct {
|
|||
&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),
|
||||
|
@ -823,43 +656,7 @@ var validParseTestCases = []struct {
|
|||
},
|
||||
},
|
||||
},
|
||||
OrigValue: &Operator{
|
||||
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,
|
||||
Assigner: "=",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
|
@ -882,13 +679,7 @@ var validParseTestCases = []struct {
|
|||
Value: 1000000,
|
||||
Token: "1000000",
|
||||
},
|
||||
OrigValue: &Int64{
|
||||
LiteralPos: mkpos(9, 2, 9),
|
||||
Value: 1000000,
|
||||
Token: "1000000",
|
||||
},
|
||||
Assigner: "=",
|
||||
Referenced: true,
|
||||
Assigner: "=",
|
||||
},
|
||||
&Assignment{
|
||||
Name: "bar",
|
||||
|
@ -897,23 +688,8 @@ var validParseTestCases = []struct {
|
|||
Value: &Variable{
|
||||
Name: "foo",
|
||||
NamePos: mkpos(25, 3, 9),
|
||||
Value: &Int64{
|
||||
LiteralPos: mkpos(9, 2, 9),
|
||||
Value: 1000000,
|
||||
Token: "1000000",
|
||||
},
|
||||
},
|
||||
OrigValue: &Variable{
|
||||
Name: "foo",
|
||||
NamePos: mkpos(25, 3, 9),
|
||||
Value: &Int64{
|
||||
LiteralPos: mkpos(9, 2, 9),
|
||||
Value: 1000000,
|
||||
Token: "1000000",
|
||||
},
|
||||
},
|
||||
Assigner: "=",
|
||||
Referenced: true,
|
||||
Assigner: "=",
|
||||
},
|
||||
&Assignment{
|
||||
Name: "baz",
|
||||
|
@ -922,164 +698,26 @@ var validParseTestCases = []struct {
|
|||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
OrigValue: &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: "=",
|
||||
Referenced: true,
|
||||
Assigner: "=",
|
||||
},
|
||||
&Assignment{
|
||||
Name: "boo",
|
||||
NamePos: mkpos(49, 5, 3),
|
||||
EqualsPos: mkpos(53, 5, 7),
|
||||
Value: &Operator{
|
||||
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{
|
||||
Value: &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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Assigner: "=",
|
||||
},
|
||||
|
@ -1090,20 +728,6 @@ var validParseTestCases = []struct {
|
|||
Value: &Variable{
|
||||
Name: "foo",
|
||||
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: "+=",
|
||||
},
|
||||
|
@ -1171,7 +795,7 @@ func TestParseValidInput(t *testing.T) {
|
|||
for i, testCase := range validParseTestCases {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
r := bytes.NewBufferString(testCase.input)
|
||||
file, errs := ParseAndEval("", r, NewScope(nil))
|
||||
file, errs := Parse("", r)
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("test case: %s", testCase.input)
|
||||
t.Errorf("unexpected errors:")
|
||||
|
@ -1236,6 +860,17 @@ func TestParserError(t *testing.T) {
|
|||
`,
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -1284,7 +919,7 @@ func TestParserEndPos(t *testing.T) {
|
|||
|
||||
r := bytes.NewBufferString(in)
|
||||
|
||||
file, errs := ParseAndEval("", r, NewScope(nil))
|
||||
file, errs := Parse("", r)
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("unexpected errors:")
|
||||
for _, err := range errs {
|
||||
|
@ -1318,9 +953,8 @@ func TestParserEndPos(t *testing.T) {
|
|||
|
||||
func TestParserNotEvaluated(t *testing.T) {
|
||||
// When parsing without evaluation, create variables correctly
|
||||
scope := NewScope(nil)
|
||||
input := "FOO=abc\n"
|
||||
_, errs := Parse("", bytes.NewBufferString(input), scope)
|
||||
file, errs := Parse("", bytes.NewBufferString(input))
|
||||
if errs != nil {
|
||||
t.Errorf("unexpected errors:")
|
||||
for _, err := range errs {
|
||||
|
@ -1328,11 +962,11 @@ func TestParserNotEvaluated(t *testing.T) {
|
|||
}
|
||||
t.FailNow()
|
||||
}
|
||||
assignment, found := scope.Get("FOO")
|
||||
if !found {
|
||||
assignment, ok := file.Defs[0].(*Assignment)
|
||||
if !ok || assignment.Name != "FOO" {
|
||||
t.Fatalf("Expected to find FOO after parsing %s", input)
|
||||
}
|
||||
if s := assignment.String(); strings.Contains(s, "PANIC") {
|
||||
t.Errorf("Attempt to print FOO returned %s", s)
|
||||
if assignment.Value.String() != "abc" {
|
||||
t.Errorf("Attempt to print FOO returned %s", assignment.Value.String())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ func (p *printer) printAssignment(assignment *Assignment) {
|
|||
p.requestSpace()
|
||||
p.printToken(assignment.Assigner, assignment.EqualsPos)
|
||||
p.requestSpace()
|
||||
p.printExpression(assignment.OrigValue)
|
||||
p.printExpression(assignment.Value)
|
||||
p.requestNewline()
|
||||
}
|
||||
|
||||
|
@ -134,7 +134,7 @@ func (p *printer) printExpression(value Expression) {
|
|||
case *Select:
|
||||
p.printSelect(v)
|
||||
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
|
||||
}
|
||||
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.pos = s.RBracePos
|
||||
return
|
||||
|
@ -196,22 +196,7 @@ func (p *printer) printSelect(s *Select) {
|
|||
p.printToken("(", p.pos)
|
||||
}
|
||||
for i, pat := range c.Patterns {
|
||||
switch pat := pat.(type) {
|
||||
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")
|
||||
}
|
||||
p.printSelectPattern(pat)
|
||||
if i < len(c.Patterns)-1 {
|
||||
p.printToken(",", p.pos)
|
||||
p.requestSpace()
|
||||
|
@ -222,7 +207,7 @@ func (p *printer) printSelect(s *Select) {
|
|||
}
|
||||
p.printToken(":", c.ColonPos)
|
||||
p.requestSpace()
|
||||
if unset, ok := c.Value.(UnsetProperty); ok {
|
||||
if unset, ok := c.Value.(*UnsetProperty); ok {
|
||||
p.printToken(unset.String(), unset.Pos())
|
||||
} else {
|
||||
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) {
|
||||
p.requestSpace()
|
||||
p.printToken("[", pos)
|
||||
|
|
|
@ -733,6 +733,26 @@ foo {
|
|||
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:]
|
||||
|
||||
r := bytes.NewBufferString(in)
|
||||
file, errs := Parse("", r, NewScope(nil))
|
||||
file, errs := Parse("", r)
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("test case: %s", in)
|
||||
t.Errorf("unexpected errors:")
|
||||
|
|
|
@ -282,8 +282,8 @@ func isListOfPrimitives(values []Expression) bool {
|
|||
if len(values) == 0 {
|
||||
return true
|
||||
}
|
||||
switch values[0].Type() {
|
||||
case BoolType, StringType, Int64Type:
|
||||
switch values[0].(type) {
|
||||
case *Bool, *String, *Int64:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/google/blueprint/optional"
|
||||
"github.com/google/blueprint/parser"
|
||||
)
|
||||
|
||||
// ConfigurableOptional is the same as ShallowOptional, but we use this separate
|
||||
|
@ -150,6 +151,17 @@ type ConfigurableValue struct {
|
|||
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 {
|
||||
switch c.typ {
|
||||
case configurableValueTypeString:
|
||||
|
@ -193,6 +205,7 @@ const (
|
|||
configurablePatternTypeString configurablePatternType = iota
|
||||
configurablePatternTypeBool
|
||||
configurablePatternTypeDefault
|
||||
configurablePatternTypeAny
|
||||
)
|
||||
|
||||
func (v *configurablePatternType) String() string {
|
||||
|
@ -203,6 +216,8 @@ func (v *configurablePatternType) String() string {
|
|||
return "bool"
|
||||
case configurablePatternTypeDefault:
|
||||
return "default"
|
||||
case configurablePatternTypeAny:
|
||||
return "any"
|
||||
default:
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
@ -222,6 +237,7 @@ type ConfigurablePattern struct {
|
|||
typ configurablePatternType
|
||||
stringValue string
|
||||
boolValue bool
|
||||
binding string
|
||||
}
|
||||
|
||||
func NewStringConfigurablePattern(s string) ConfigurablePattern {
|
||||
|
@ -251,6 +267,9 @@ func (p *ConfigurablePattern) matchesValue(v ConfigurableValue) bool {
|
|||
if v.typ == configurableValueTypeUndefined {
|
||||
return false
|
||||
}
|
||||
if p.typ == configurablePatternTypeAny {
|
||||
return true
|
||||
}
|
||||
if p.typ != v.typ.patternType() {
|
||||
return false
|
||||
}
|
||||
|
@ -271,6 +290,9 @@ func (p *ConfigurablePattern) matchesValueType(v ConfigurableValue) bool {
|
|||
if v.typ == configurableValueTypeUndefined {
|
||||
return true
|
||||
}
|
||||
if p.typ == configurablePatternTypeAny {
|
||||
return true
|
||||
}
|
||||
return p.typ == v.typ.patternType()
|
||||
}
|
||||
|
||||
|
@ -282,27 +304,46 @@ func (p *ConfigurablePattern) matchesValueType(v ConfigurableValue) bool {
|
|||
// different configurable properties.
|
||||
type ConfigurableCase[T ConfigurableElements] struct {
|
||||
patterns []ConfigurablePattern
|
||||
value *T
|
||||
value parser.Expression
|
||||
}
|
||||
|
||||
type configurableCaseReflection interface {
|
||||
initialize(patterns []ConfigurablePattern, value interface{})
|
||||
initialize(patterns []ConfigurablePattern, value parser.Expression)
|
||||
}
|
||||
|
||||
var _ configurableCaseReflection = &ConfigurableCase[string]{}
|
||||
|
||||
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
|
||||
patterns = slices.Clone(patterns)
|
||||
return ConfigurableCase[T]{
|
||||
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.value = value.(*T)
|
||||
c.value = value
|
||||
}
|
||||
|
||||
// for the given T, return the reflect.type of configurableCase[T]
|
||||
|
@ -371,6 +412,22 @@ type Configurable[T ConfigurableElements] struct {
|
|||
marker configurableMarker
|
||||
propertyName string
|
||||
inner *configurableInner[T]
|
||||
// See Configurable.evaluate for a description of the postProcessor algorithm and
|
||||
// why this is a 2d list
|
||||
postProcessors *[][]postProcessor[T]
|
||||
}
|
||||
|
||||
type postProcessor[T ConfigurableElements] struct {
|
||||
f func(T) T
|
||||
// start and end represent the range of configurableInners
|
||||
// that this postprocessor is applied to. When appending two configurables
|
||||
// together, the start and end values will stay the same for the left
|
||||
// configurable's postprocessors, but the rights will be rebased by the
|
||||
// number of configurableInners in the left configurable. This way
|
||||
// the postProcessors still only apply to the configurableInners they
|
||||
// origionally applied to before the appending.
|
||||
start int
|
||||
end int
|
||||
}
|
||||
|
||||
type configurableInner[T ConfigurableElements] struct {
|
||||
|
@ -384,6 +441,7 @@ type configurableInner[T ConfigurableElements] struct {
|
|||
type singleConfigurable[T ConfigurableElements] struct {
|
||||
conditions []ConfigurableCondition
|
||||
cases []ConfigurableCase[T]
|
||||
scope *parser.Scope
|
||||
}
|
||||
|
||||
// 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
|
||||
conditions = slices.Clone(conditions)
|
||||
cases = slices.Clone(cases)
|
||||
var zeroPostProcessors [][]postProcessor[T]
|
||||
return Configurable[T]{
|
||||
inner: &configurableInner[T]{
|
||||
single: singleConfigurable[T]{
|
||||
|
@ -405,35 +464,81 @@ func NewConfigurable[T ConfigurableElements](conditions []ConfigurableCondition,
|
|||
cases: cases,
|
||||
},
|
||||
},
|
||||
postProcessors: &zeroPostProcessors,
|
||||
}
|
||||
}
|
||||
|
||||
func newConfigurableWithPropertyName[T ConfigurableElements](propertyName string, conditions []ConfigurableCondition, cases []ConfigurableCase[T], addScope bool) Configurable[T] {
|
||||
result := NewConfigurable(conditions, cases)
|
||||
result.propertyName = propertyName
|
||||
if addScope {
|
||||
for curr := result.inner; curr != nil; curr = curr.next {
|
||||
curr.single.scope = parser.NewScope(nil)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *Configurable[T]) AppendSimpleValue(value T) {
|
||||
value = copyConfiguredValue(value)
|
||||
// This may be a property that was never initialized from a bp file
|
||||
if c.inner == nil {
|
||||
c.inner = &configurableInner[T]{
|
||||
single: singleConfigurable[T]{
|
||||
cases: []ConfigurableCase[T]{{
|
||||
value: &value,
|
||||
}},
|
||||
},
|
||||
}
|
||||
c.initialize(nil, "", nil, []ConfigurableCase[T]{{
|
||||
value: configuredValueToExpression(value),
|
||||
}})
|
||||
return
|
||||
}
|
||||
c.inner.appendSimpleValue(value)
|
||||
}
|
||||
|
||||
// AddPostProcessor adds a function that will modify the result of
|
||||
// Get() when Get() is called. It operates on all the current contents
|
||||
// of the Configurable property, but if other values are appended to
|
||||
// the Configurable property afterwards, the postProcessor will not run
|
||||
// on them. This can be useful to essentially modify a configurable
|
||||
// property without evaluating it.
|
||||
func (c *Configurable[T]) AddPostProcessor(p func(T) T) {
|
||||
// Add the new postProcessor on top of the tallest stack of postProcessors.
|
||||
// See Configurable.evaluate for more details on the postProcessors algorithm
|
||||
// and data structure.
|
||||
num_links := c.inner.numLinks()
|
||||
if c.postProcessors == nil {
|
||||
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.
|
||||
// A configurable property may be unset, in which case Get will return nil.
|
||||
func (c *Configurable[T]) Get(evaluator ConfigurableEvaluator) ConfigurableOptional[T] {
|
||||
result := c.inner.evaluate(c.propertyName, evaluator)
|
||||
result := c.evaluate(c.propertyName, evaluator)
|
||||
return configuredValuePtrToOptional(result)
|
||||
}
|
||||
|
||||
// GetOrDefault is the same as Get, but will return the provided default value if the property was unset.
|
||||
func (c *Configurable[T]) GetOrDefault(evaluator ConfigurableEvaluator, defaultValue T) T {
|
||||
result := c.inner.evaluate(c.propertyName, evaluator)
|
||||
result := c.evaluate(c.propertyName, evaluator)
|
||||
if result != nil {
|
||||
// Copy the result so that it can't be changed from soong
|
||||
return copyConfiguredValue(*result)
|
||||
|
@ -441,6 +546,127 @@ func (c *Configurable[T]) GetOrDefault(evaluator ConfigurableEvaluator, defaultV
|
|||
return defaultValue
|
||||
}
|
||||
|
||||
type valueAndIndices[T ConfigurableElements] struct {
|
||||
value *T
|
||||
replace bool
|
||||
// Similar to start/end in postProcessor, these represent the origional
|
||||
// range or configurableInners that this merged group represents. It's needed
|
||||
// in order to apply recursive postProcessors to only the relevant
|
||||
// configurableInners, even after those configurableInners have been merged
|
||||
// in order to apply an earlier postProcessor.
|
||||
start int
|
||||
end int
|
||||
}
|
||||
|
||||
func (c *Configurable[T]) evaluate(propertyName string, evaluator ConfigurableEvaluator) *T {
|
||||
if c.inner == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(*c.postProcessors) == 0 {
|
||||
// Use a simpler algorithm if there are no postprocessors
|
||||
return c.inner.evaluate(propertyName, evaluator)
|
||||
}
|
||||
|
||||
// The basic idea around evaluating with postprocessors is that each individual
|
||||
// node in the chain (each configurableInner) is first evaluated, and then when
|
||||
// a postprocessor operates on a certain range, that range is merged before passing
|
||||
// it to the postprocessor. We want postProcessors to only accept a final merged
|
||||
// value instead of a linked list, but at the same time, only operate over a portion
|
||||
// of the list. If more configurables are appended onto this one, their values won't
|
||||
// be operated on by the existing postProcessors, but they may have their own
|
||||
// postprocessors.
|
||||
//
|
||||
// _____________________
|
||||
// | __________|
|
||||
// ______ | _____| ___
|
||||
// | | | | | |
|
||||
// a -> b -> c -> d -> e -> f -> g
|
||||
//
|
||||
// In this diagram, the letters along the bottom is the chain of configurableInners.
|
||||
// The brackets on top represent postprocessors, where higher brackets are processed
|
||||
// after lower ones.
|
||||
//
|
||||
// To evaluate this example, first we evaluate the raw values for all nodes a->g.
|
||||
// Then we merge nodes a/b and d/e and apply the postprocessors to their merged values,
|
||||
// and also to g. Those merged and postprocessed nodes are then reinserted into the
|
||||
// list, and we move on to doing the higher level postprocessors (starting with the c->e one)
|
||||
// in the same way. When all postprocessors are done, a final merge is done on anything
|
||||
// leftover.
|
||||
//
|
||||
// The Configurable.postProcessors field is a 2d array to represent this hierarchy.
|
||||
// The outer index moves right on this graph, the inner index goes up.
|
||||
// When adding a new postProcessor, it will always be the last postProcessor to run
|
||||
// until another is added or another configurable is appended. So in AddPostProcessor(),
|
||||
// we add it to the tallest existing stack.
|
||||
|
||||
var currentValues []valueAndIndices[T]
|
||||
for curr, i := c.inner, 0; curr != nil; curr, i = curr.next, i+1 {
|
||||
value := curr.single.evaluateNonTransitive(propertyName, evaluator)
|
||||
currentValues = append(currentValues, valueAndIndices[T]{
|
||||
value: value,
|
||||
replace: curr.replace,
|
||||
start: i,
|
||||
end: i + 1,
|
||||
})
|
||||
}
|
||||
|
||||
if c.postProcessors == nil || len(*c.postProcessors) == 0 {
|
||||
return mergeValues(currentValues).value
|
||||
}
|
||||
|
||||
foundPostProcessor := true
|
||||
for depth := 0; foundPostProcessor; depth++ {
|
||||
foundPostProcessor = false
|
||||
var newValues []valueAndIndices[T]
|
||||
i := 0
|
||||
for _, postProcessorGroup := range *c.postProcessors {
|
||||
if len(postProcessorGroup) > depth {
|
||||
foundPostProcessor = true
|
||||
postProcessor := postProcessorGroup[depth]
|
||||
startI := 0
|
||||
endI := 0
|
||||
for currentValues[startI].start < postProcessor.start {
|
||||
startI++
|
||||
}
|
||||
for currentValues[endI].end < postProcessor.end {
|
||||
endI++
|
||||
}
|
||||
endI++
|
||||
newValues = append(newValues, currentValues[i:startI]...)
|
||||
merged := mergeValues(currentValues[startI:endI])
|
||||
if merged.value != nil {
|
||||
processed := postProcessor.f(*merged.value)
|
||||
merged.value = &processed
|
||||
}
|
||||
newValues = append(newValues, merged)
|
||||
i = endI
|
||||
}
|
||||
}
|
||||
newValues = append(newValues, currentValues[i:]...)
|
||||
currentValues = newValues
|
||||
}
|
||||
|
||||
return mergeValues(currentValues).value
|
||||
}
|
||||
|
||||
func mergeValues[T ConfigurableElements](values []valueAndIndices[T]) valueAndIndices[T] {
|
||||
if len(values) < 0 {
|
||||
panic("Expected at least 1 value in mergeValues")
|
||||
}
|
||||
result := values[0]
|
||||
for i := 1; i < len(values); i++ {
|
||||
if result.replace {
|
||||
result.value = replaceConfiguredValues(result.value, values[i].value)
|
||||
} else {
|
||||
result.value = appendConfiguredValues(result.value, values[i].value)
|
||||
}
|
||||
result.end = values[i].end
|
||||
result.replace = values[i].replace
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *configurableInner[T]) evaluate(propertyName string, evaluator ConfigurableEvaluator) *T {
|
||||
if c == nil {
|
||||
return nil
|
||||
|
@ -472,7 +698,12 @@ func (c *singleConfigurable[T]) evaluateNonTransitive(propertyName string, evalu
|
|||
if len(c.cases) == 0 {
|
||||
return nil
|
||||
} 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 {
|
||||
evaluator.PropertyErrorf(propertyName, "Expected 0 or 1 branches in an unconfigured select, found %d", len(c.cases))
|
||||
return nil
|
||||
|
@ -499,7 +730,13 @@ func (c *singleConfigurable[T]) evaluateNonTransitive(propertyName string, evalu
|
|||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -511,6 +748,27 @@ func (c *singleConfigurable[T]) evaluateNonTransitive(propertyName string, evalu
|
|||
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 {
|
||||
if a == nil && b == nil {
|
||||
return nil
|
||||
|
@ -579,20 +837,27 @@ type configurableReflection interface {
|
|||
// Same as configurableReflection, but since initialize needs to take a pointer
|
||||
// to a Configurable, it was broken out into a separate 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 _ 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.inner = &configurableInner[T]{
|
||||
single: singleConfigurable[T]{
|
||||
conditions: conditions,
|
||||
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) {
|
||||
|
@ -600,12 +865,37 @@ func (c Configurable[T]) setAppend(append any, replace bool, prepend bool) {
|
|||
if a.inner.isEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
if prepend {
|
||||
newBase := a.inner.numLinks()
|
||||
*c.postProcessors = appendPostprocessors(*a.postProcessors, *c.postProcessors, newBase)
|
||||
} else {
|
||||
newBase := c.inner.numLinks()
|
||||
*c.postProcessors = appendPostprocessors(*c.postProcessors, *a.postProcessors, newBase)
|
||||
}
|
||||
|
||||
c.inner.setAppend(a.inner, replace, prepend)
|
||||
if c.inner == c.inner.next {
|
||||
panic("pointer loop")
|
||||
}
|
||||
}
|
||||
|
||||
func appendPostprocessors[T ConfigurableElements](a, b [][]postProcessor[T], newBase int) [][]postProcessor[T] {
|
||||
var result [][]postProcessor[T]
|
||||
for i := 0; i < len(a); i++ {
|
||||
result = append(result, slices.Clone(a[i]))
|
||||
}
|
||||
for i := 0; i < len(b); i++ {
|
||||
n := slices.Clone(b[i])
|
||||
for j := 0; j < len(n); j++ {
|
||||
n[j].start += newBase
|
||||
n[j].end += newBase
|
||||
}
|
||||
result = append(result, n)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *configurableInner[T]) setAppend(append *configurableInner[T], replace bool, prepend bool) {
|
||||
if c.isEmpty() {
|
||||
*c = *append.clone()
|
||||
|
@ -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) {
|
||||
if c.next == nil {
|
||||
c.replace = false
|
||||
c.next = &configurableInner[T]{
|
||||
single: singleConfigurable[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 {
|
||||
continue
|
||||
}
|
||||
switch v := any(c.value).(type) {
|
||||
case *string:
|
||||
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
|
||||
}
|
||||
}
|
||||
if err := c.value.PrintfInto(value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printfIntoString(s *string, configValue string) error {
|
||||
count := strings.Count(*s, "%")
|
||||
if count == 0 {
|
||||
return nil
|
||||
func (c Configurable[T]) clone() any {
|
||||
var newPostProcessors *[][]postProcessor[T]
|
||||
if c.postProcessors != nil {
|
||||
x := appendPostprocessors(*c.postProcessors, nil, 0)
|
||||
newPostProcessors = &x
|
||||
}
|
||||
|
||||
if count > 1 {
|
||||
return fmt.Errorf("list/value variable properties only support a single '%%'")
|
||||
return Configurable[T]{
|
||||
propertyName: c.propertyName,
|
||||
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 {
|
||||
return Configurable[T]{
|
||||
propertyName: c.propertyName,
|
||||
inner: c.inner.clone(),
|
||||
}
|
||||
func (c Configurable[T]) Clone() Configurable[T] {
|
||||
return c.clone().(Configurable[T])
|
||||
}
|
||||
|
||||
func (c *configurableInner[T]) clone() *configurableInner[T] {
|
||||
|
@ -755,6 +1035,9 @@ func (c *singleConfigurable[T]) isEmpty() bool {
|
|||
return false
|
||||
}
|
||||
if len(c.cases) == 1 && c.cases[0].value != nil {
|
||||
if _, ok := c.cases[0].value.(*parser.UnsetProperty); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
@ -774,7 +1057,7 @@ func (c *singleConfigurable[T]) alwaysHasValue() bool {
|
|||
return false
|
||||
}
|
||||
for _, c := range c.cases {
|
||||
if c.value == nil {
|
||||
if _, isUnset := c.value.(*parser.UnsetProperty); isUnset || c.value == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -785,17 +1068,84 @@ func (c Configurable[T]) configuredType() reflect.Type {
|
|||
return reflect.TypeOf((*T)(nil)).Elem()
|
||||
}
|
||||
|
||||
func copyConfiguredValuePtr[T ConfigurableElements](t *T) *T {
|
||||
if t == nil {
|
||||
return nil
|
||||
func expressionToConfiguredValue[T ConfigurableElements](expr parser.Expression, scope *parser.Scope) (*T, error) {
|
||||
expr, err := expr.Eval(scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch t2 := any(*t).(type) {
|
||||
case []string:
|
||||
result := any(slices.Clone(t2)).(T)
|
||||
return &result
|
||||
switch e := expr.(type) {
|
||||
case *parser.UnsetProperty:
|
||||
return nil, nil
|
||||
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:
|
||||
x := *t
|
||||
return &x
|
||||
// If the expression was not evaluated beforehand we could hit this error even when the types match,
|
||||
// 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
|
||||
// with the provided string value. It's intention is to support soong config value variables
|
||||
// on Configurable properties.
|
||||
func PrintfIntoConfigurable(c any, value string) error {
|
||||
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()))
|
||||
}
|
||||
}
|
||||
|
|
95
proptools/configurable_test.go
Normal file
95
proptools/configurable_test.go
Normal 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)
|
|
@ -473,33 +473,7 @@ func ExtendBasicType(dstFieldValue, srcFieldValue reflect.Value, order Order) {
|
|||
// structs when they want to change the default values of properties.
|
||||
srcFieldType := srcFieldValue.Type()
|
||||
if isConfigurable(dstFieldValue.Type()) && !isConfigurable(srcFieldType) {
|
||||
var value reflect.Value
|
||||
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()
|
||||
srcFieldValue = promoteValueToConfigurable(srcFieldValue)
|
||||
}
|
||||
|
||||
switch srcFieldValue.Kind() {
|
||||
|
|
|
@ -20,6 +20,8 @@ import (
|
|||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/blueprint/parser"
|
||||
)
|
||||
|
||||
type appendPropertyTestCase struct {
|
||||
|
@ -1257,172 +1259,166 @@ func appendPropertiesTestCases() []appendPropertyTestCase {
|
|||
{
|
||||
name: "Append configurable",
|
||||
dst: &struct{ S Configurable[[]string] }{
|
||||
S: Configurable[[]string]{
|
||||
inner: &configurableInner[[]string]{
|
||||
single: singleConfigurable[[]string]{
|
||||
conditions: []ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"foo",
|
||||
},
|
||||
}},
|
||||
cases: []ConfigurableCase[[]string]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "a",
|
||||
}},
|
||||
value: &[]string{"1", "2"},
|
||||
}},
|
||||
},
|
||||
S: NewConfigurable[[]string]([]ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"foo",
|
||||
},
|
||||
},
|
||||
}},
|
||||
[]ConfigurableCase[[]string]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "a",
|
||||
}},
|
||||
value: &parser.List{Values: []parser.Expression{
|
||||
&parser.String{Value: "1"},
|
||||
&parser.String{Value: "2"},
|
||||
}},
|
||||
}},
|
||||
),
|
||||
},
|
||||
src: &struct{ S Configurable[[]string] }{
|
||||
S: Configurable[[]string]{
|
||||
inner: &configurableInner[[]string]{
|
||||
single: singleConfigurable[[]string]{
|
||||
conditions: []ConfigurableCondition{{
|
||||
functionName: "release_variable",
|
||||
args: []string{
|
||||
"bar",
|
||||
},
|
||||
}},
|
||||
cases: []ConfigurableCase[[]string]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "b",
|
||||
}},
|
||||
value: &[]string{"3", "4"},
|
||||
}},
|
||||
},
|
||||
S: NewConfigurable([]ConfigurableCondition{{
|
||||
functionName: "release_variable",
|
||||
args: []string{
|
||||
"bar",
|
||||
},
|
||||
},
|
||||
}},
|
||||
[]ConfigurableCase[[]string]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "b",
|
||||
}},
|
||||
value: &parser.List{Values: []parser.Expression{
|
||||
&parser.String{Value: "3"},
|
||||
&parser.String{Value: "4"},
|
||||
}},
|
||||
}},
|
||||
),
|
||||
},
|
||||
out: &struct{ S Configurable[[]string] }{
|
||||
S: Configurable[[]string]{
|
||||
inner: &configurableInner[[]string]{
|
||||
single: singleConfigurable[[]string]{
|
||||
conditions: []ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"foo",
|
||||
},
|
||||
}},
|
||||
cases: []ConfigurableCase[[]string]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "a",
|
||||
}},
|
||||
value: &[]string{"1", "2"},
|
||||
}},
|
||||
S: func() Configurable[[]string] {
|
||||
result := NewConfigurable([]ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"foo",
|
||||
},
|
||||
next: &configurableInner[[]string]{
|
||||
single: singleConfigurable[[]string]{
|
||||
conditions: []ConfigurableCondition{{
|
||||
functionName: "release_variable",
|
||||
args: []string{
|
||||
"bar",
|
||||
},
|
||||
}},
|
||||
cases: []ConfigurableCase[[]string]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "b",
|
||||
}},
|
||||
value: &[]string{"3", "4"},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
[]ConfigurableCase[[]string]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "a",
|
||||
}},
|
||||
value: &parser.List{Values: []parser.Expression{
|
||||
&parser.String{Value: "1"},
|
||||
&parser.String{Value: "2"},
|
||||
}},
|
||||
}},
|
||||
)
|
||||
result.Append(NewConfigurable([]ConfigurableCondition{{
|
||||
functionName: "release_variable",
|
||||
args: []string{
|
||||
"bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
[]ConfigurableCase[[]string]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "b",
|
||||
}},
|
||||
value: &parser.List{Values: []parser.Expression{
|
||||
&parser.String{Value: "3"},
|
||||
&parser.String{Value: "4"},
|
||||
}},
|
||||
}}))
|
||||
return result
|
||||
}(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Prepend configurable",
|
||||
order: Prepend,
|
||||
dst: &struct{ S Configurable[[]string] }{
|
||||
S: Configurable[[]string]{
|
||||
inner: &configurableInner[[]string]{
|
||||
single: singleConfigurable[[]string]{
|
||||
conditions: []ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"foo",
|
||||
},
|
||||
}},
|
||||
cases: []ConfigurableCase[[]string]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "a",
|
||||
}},
|
||||
value: &[]string{"1", "2"},
|
||||
}},
|
||||
},
|
||||
S: NewConfigurable([]ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"foo",
|
||||
},
|
||||
},
|
||||
}},
|
||||
[]ConfigurableCase[[]string]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "a",
|
||||
}},
|
||||
value: &parser.List{Values: []parser.Expression{
|
||||
&parser.String{Value: "1"},
|
||||
&parser.String{Value: "2"},
|
||||
}},
|
||||
}},
|
||||
),
|
||||
},
|
||||
src: &struct{ S Configurable[[]string] }{
|
||||
S: Configurable[[]string]{
|
||||
inner: &configurableInner[[]string]{
|
||||
single: singleConfigurable[[]string]{
|
||||
conditions: []ConfigurableCondition{{
|
||||
functionName: "release_variable",
|
||||
args: []string{
|
||||
"bar",
|
||||
},
|
||||
}},
|
||||
cases: []ConfigurableCase[[]string]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "b",
|
||||
}},
|
||||
value: &[]string{"3", "4"},
|
||||
}},
|
||||
},
|
||||
S: NewConfigurable([]ConfigurableCondition{{
|
||||
functionName: "release_variable",
|
||||
args: []string{
|
||||
"bar",
|
||||
},
|
||||
},
|
||||
}},
|
||||
[]ConfigurableCase[[]string]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "b",
|
||||
}},
|
||||
value: &parser.List{Values: []parser.Expression{
|
||||
&parser.String{Value: "3"},
|
||||
&parser.String{Value: "4"},
|
||||
}},
|
||||
}},
|
||||
),
|
||||
},
|
||||
out: &struct{ S Configurable[[]string] }{
|
||||
S: Configurable[[]string]{
|
||||
inner: &configurableInner[[]string]{
|
||||
single: singleConfigurable[[]string]{
|
||||
conditions: []ConfigurableCondition{{
|
||||
functionName: "release_variable",
|
||||
args: []string{
|
||||
"bar",
|
||||
},
|
||||
}},
|
||||
cases: []ConfigurableCase[[]string]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "b",
|
||||
}},
|
||||
value: &[]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"},
|
||||
}},
|
||||
S: func() Configurable[[]string] {
|
||||
result := NewConfigurable(
|
||||
[]ConfigurableCondition{{
|
||||
functionName: "release_variable",
|
||||
args: []string{
|
||||
"bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
[]ConfigurableCase[[]string]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "b",
|
||||
}},
|
||||
value: &parser.List{Values: []parser.Expression{
|
||||
&parser.String{Value: "3"},
|
||||
&parser.String{Value: "4"},
|
||||
}},
|
||||
}},
|
||||
)
|
||||
result.Append(NewConfigurable(
|
||||
[]ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"foo",
|
||||
},
|
||||
}},
|
||||
[]ConfigurableCase[[]string]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "a",
|
||||
}},
|
||||
value: &parser.List{Values: []parser.Expression{
|
||||
&parser.String{Value: "1"},
|
||||
&parser.String{Value: "2"},
|
||||
}},
|
||||
}}))
|
||||
return result
|
||||
}(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -1892,31 +1888,24 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase {
|
|||
order: Append,
|
||||
dst: []interface{}{
|
||||
&struct{ S Configurable[bool] }{
|
||||
S: Configurable[bool]{
|
||||
inner: &configurableInner[bool]{
|
||||
single: singleConfigurable[bool]{
|
||||
conditions: []ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"foo",
|
||||
},
|
||||
}},
|
||||
cases: []ConfigurableCase[bool]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "a",
|
||||
}},
|
||||
value: BoolPtr(true),
|
||||
}, {
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeDefault,
|
||||
}},
|
||||
value: BoolPtr(false),
|
||||
}},
|
||||
},
|
||||
S: NewConfigurable[bool]([]ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"foo",
|
||||
},
|
||||
},
|
||||
}}, []ConfigurableCase[bool]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "a",
|
||||
}},
|
||||
value: &parser.Bool{Value: true},
|
||||
}, {
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeDefault,
|
||||
}},
|
||||
value: &parser.Bool{Value: false},
|
||||
}}),
|
||||
},
|
||||
},
|
||||
src: &struct{ S *bool }{
|
||||
|
@ -1924,38 +1913,30 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase {
|
|||
},
|
||||
out: []interface{}{
|
||||
&struct{ S Configurable[bool] }{
|
||||
S: Configurable[bool]{
|
||||
inner: &configurableInner[bool]{
|
||||
single: singleConfigurable[bool]{
|
||||
conditions: []ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"foo",
|
||||
},
|
||||
}},
|
||||
cases: []ConfigurableCase[bool]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "a",
|
||||
}},
|
||||
value: BoolPtr(true),
|
||||
}, {
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeDefault,
|
||||
}},
|
||||
value: BoolPtr(false),
|
||||
}},
|
||||
S: func() Configurable[bool] {
|
||||
result := NewConfigurable[bool]([]ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"foo",
|
||||
},
|
||||
next: &configurableInner[bool]{
|
||||
single: singleConfigurable[bool]{
|
||||
cases: []ConfigurableCase[bool]{{
|
||||
value: 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
|
||||
}(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1964,31 +1945,26 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase {
|
|||
order: Append,
|
||||
dst: []interface{}{
|
||||
&struct{ S Configurable[bool] }{
|
||||
S: Configurable[bool]{
|
||||
inner: &configurableInner[bool]{
|
||||
single: singleConfigurable[bool]{
|
||||
conditions: []ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"foo",
|
||||
},
|
||||
}},
|
||||
cases: []ConfigurableCase[bool]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "a",
|
||||
}},
|
||||
value: BoolPtr(true),
|
||||
}, {
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeDefault,
|
||||
}},
|
||||
value: BoolPtr(false),
|
||||
}},
|
||||
},
|
||||
S: NewConfigurable[bool]([]ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"foo",
|
||||
},
|
||||
},
|
||||
}},
|
||||
[]ConfigurableCase[bool]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "a",
|
||||
}},
|
||||
value: &parser.Bool{Value: true},
|
||||
}, {
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeDefault,
|
||||
}},
|
||||
value: &parser.Bool{Value: false},
|
||||
}},
|
||||
),
|
||||
},
|
||||
},
|
||||
src: &struct{ S bool }{
|
||||
|
@ -1996,38 +1972,31 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase {
|
|||
},
|
||||
out: []interface{}{
|
||||
&struct{ S Configurable[bool] }{
|
||||
S: Configurable[bool]{
|
||||
inner: &configurableInner[bool]{
|
||||
single: singleConfigurable[bool]{
|
||||
conditions: []ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"foo",
|
||||
},
|
||||
}},
|
||||
cases: []ConfigurableCase[bool]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "a",
|
||||
}},
|
||||
value: BoolPtr(true),
|
||||
}, {
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeDefault,
|
||||
}},
|
||||
value: BoolPtr(false),
|
||||
}},
|
||||
},
|
||||
next: &configurableInner[bool]{
|
||||
single: singleConfigurable[bool]{
|
||||
cases: []ConfigurableCase[bool]{{
|
||||
value: BoolPtr(true),
|
||||
}},
|
||||
S: func() Configurable[bool] {
|
||||
result := NewConfigurable[bool](
|
||||
[]ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
[]ConfigurableCase[bool]{{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "a",
|
||||
}},
|
||||
value: &parser.Bool{Value: true},
|
||||
}, {
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeDefault,
|
||||
}},
|
||||
value: &parser.Bool{Value: false},
|
||||
}},
|
||||
)
|
||||
result.AppendSimpleValue(true)
|
||||
return result
|
||||
}(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -158,7 +158,7 @@ func (ctx *unpackContext) buildPropertyMap(prefix string, properties []*parser.P
|
|||
}
|
||||
|
||||
ctx.propertyMap[name] = &packedProperty{property, false}
|
||||
switch propValue := property.Value.Eval().(type) {
|
||||
switch propValue := property.Value.(type) {
|
||||
case *parser.Map:
|
||||
ctx.buildPropertyMap(name, propValue.Properties)
|
||||
case *parser.List:
|
||||
|
@ -313,7 +313,7 @@ func (ctx *unpackContext) unpackToStruct(namePrefix string, structValue reflect.
|
|||
return
|
||||
}
|
||||
} else if isStruct(fieldValue.Type()) {
|
||||
if property.Value.Eval().Type() != parser.MapType {
|
||||
if property.Value.Type() != parser.MapType {
|
||||
ctx.addError(&UnpackError{
|
||||
fmt.Errorf("can't assign %s value to map property %q",
|
||||
property.Value.Type(), property.Name),
|
||||
|
@ -354,15 +354,17 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa
|
|||
})
|
||||
return reflect.New(configurableType), false
|
||||
}
|
||||
var postProcessors [][]postProcessor[string]
|
||||
result := Configurable[string]{
|
||||
propertyName: property.Name,
|
||||
inner: &configurableInner[string]{
|
||||
single: singleConfigurable[string]{
|
||||
cases: []ConfigurableCase[string]{{
|
||||
value: &v.Value,
|
||||
value: v,
|
||||
}},
|
||||
},
|
||||
},
|
||||
postProcessors: &postProcessors,
|
||||
}
|
||||
return reflect.ValueOf(&result), true
|
||||
case *parser.Bool:
|
||||
|
@ -374,15 +376,17 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa
|
|||
})
|
||||
return reflect.New(configurableType), false
|
||||
}
|
||||
var postProcessors [][]postProcessor[bool]
|
||||
result := Configurable[bool]{
|
||||
propertyName: property.Name,
|
||||
inner: &configurableInner[bool]{
|
||||
single: singleConfigurable[bool]{
|
||||
cases: []ConfigurableCase[bool]{{
|
||||
value: &v.Value,
|
||||
value: v,
|
||||
}},
|
||||
},
|
||||
},
|
||||
postProcessors: &postProcessors,
|
||||
}
|
||||
return reflect.ValueOf(&result), true
|
||||
case *parser.List:
|
||||
|
@ -411,26 +415,22 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa
|
|||
value[i] = exprUnpacked.Interface().(string)
|
||||
}
|
||||
}
|
||||
var postProcessors [][]postProcessor[[]string]
|
||||
result := Configurable[[]string]{
|
||||
propertyName: property.Name,
|
||||
inner: &configurableInner[[]string]{
|
||||
single: singleConfigurable[[]string]{
|
||||
cases: []ConfigurableCase[[]string]{{
|
||||
value: &value,
|
||||
value: v,
|
||||
}},
|
||||
},
|
||||
},
|
||||
postProcessors: &postProcessors,
|
||||
}
|
||||
return reflect.ValueOf(&result), true
|
||||
default:
|
||||
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:
|
||||
resultPtr := reflect.New(configurableType)
|
||||
result := resultPtr.Elem()
|
||||
|
@ -448,19 +448,15 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa
|
|||
|
||||
configurableCaseType := configurableCaseType(configuredType)
|
||||
cases := reflect.MakeSlice(reflect.SliceOf(configurableCaseType), 0, len(v.Cases))
|
||||
for i, c := range v.Cases {
|
||||
p := &parser.Property{
|
||||
Name: property.Name + "[" + strconv.Itoa(i) + "]",
|
||||
NamePos: c.ColonPos,
|
||||
Value: c.Value,
|
||||
}
|
||||
|
||||
for _, c := range v.Cases {
|
||||
patterns := make([]ConfigurablePattern, len(c.Patterns))
|
||||
for i, pat := range c.Patterns {
|
||||
switch pat := pat.(type) {
|
||||
switch pat := pat.Value.(type) {
|
||||
case *parser.String:
|
||||
if pat.Value == "__soong_conditions_default__" {
|
||||
patterns[i].typ = configurablePatternTypeDefault
|
||||
} else if pat.Value == "__soong_conditions_any__" {
|
||||
patterns[i].typ = configurablePatternTypeAny
|
||||
} else {
|
||||
patterns[i].typ = configurablePatternTypeString
|
||||
patterns[i].stringValue = pat.Value
|
||||
|
@ -471,42 +467,15 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa
|
|||
default:
|
||||
panic("unimplemented")
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
patterns[i].binding = pat.Binding.Name
|
||||
}
|
||||
|
||||
case_ := reflect.New(configurableCaseType)
|
||||
case_.Interface().(configurableCaseReflection).initialize(patterns, value.Interface())
|
||||
case_.Interface().(configurableCaseReflection).initialize(patterns, c.Value)
|
||||
cases = reflect.Append(cases, case_.Elem())
|
||||
}
|
||||
resultPtr.Interface().(configurablePtrReflection).initialize(
|
||||
v.Scope,
|
||||
property.Name,
|
||||
conditions,
|
||||
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
|
||||
// a non-configurable property. Otherwise returns nil.
|
||||
func selectOnNonConfigurablePropertyError(property *parser.Property) error {
|
||||
if _, ok := property.Value.Eval().(*parser.Select); !ok {
|
||||
if _, ok := property.Value.(*parser.Select); !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -570,7 +539,7 @@ func (ctx *unpackContext) unpackToSlice(
|
|||
// does.
|
||||
func (ctx *unpackContext) unpackToSliceInner(
|
||||
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 err := selectOnNonConfigurablePropertyError(property); err != nil {
|
||||
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.
|
||||
var getItemFunc func(*parser.Property, reflect.Type) (reflect.Value, bool)
|
||||
switch exprs[0].Type() {
|
||||
case parser.BoolType, parser.StringType, parser.Int64Type:
|
||||
getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
|
||||
getItemFunc := func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
|
||||
switch property.Value.(type) {
|
||||
case *parser.Bool, *parser.String, *parser.Int64:
|
||||
value, err := propertyToValue(t, property)
|
||||
if err != nil {
|
||||
ctx.addError(err)
|
||||
return value, false
|
||||
}
|
||||
return value, true
|
||||
}
|
||||
case parser.ListType:
|
||||
getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
|
||||
case *parser.List:
|
||||
return ctx.unpackToSlice(property.Name, property, t)
|
||||
}
|
||||
case parser.MapType:
|
||||
getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
|
||||
case *parser.Map:
|
||||
itemValue := reflect.New(t).Elem()
|
||||
ctx.unpackToStruct(property.Name, itemValue)
|
||||
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}
|
||||
|
@ -657,7 +617,7 @@ func propertyToValue(typ reflect.Type, property *parser.Property) (reflect.Value
|
|||
|
||||
switch kind := baseType.Kind(); kind {
|
||||
case reflect.Bool:
|
||||
b, ok := property.Value.Eval().(*parser.Bool)
|
||||
b, ok := property.Value.(*parser.Bool)
|
||||
if !ok {
|
||||
if err := selectOnNonConfigurablePropertyError(property); err != nil {
|
||||
return value, err
|
||||
|
@ -672,7 +632,7 @@ func propertyToValue(typ reflect.Type, property *parser.Property) (reflect.Value
|
|||
value = reflect.ValueOf(b.Value)
|
||||
|
||||
case reflect.Int64:
|
||||
b, ok := property.Value.Eval().(*parser.Int64)
|
||||
b, ok := property.Value.(*parser.Int64)
|
||||
if !ok {
|
||||
return value, &UnpackError{
|
||||
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)
|
||||
|
||||
case reflect.String:
|
||||
s, ok := property.Value.Eval().(*parser.String)
|
||||
s, ok := property.Value.(*parser.String)
|
||||
if !ok {
|
||||
if err := selectOnNonConfigurablePropertyError(property); err != nil {
|
||||
return value, err
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"bytes"
|
||||
"reflect"
|
||||
"testing"
|
||||
"text/scanner"
|
||||
|
||||
"github.com/google/blueprint/parser"
|
||||
)
|
||||
|
@ -732,16 +733,21 @@ var validUnpackTestCases = []struct {
|
|||
&struct {
|
||||
Foo Configurable[string]
|
||||
}{
|
||||
Foo: Configurable[string]{
|
||||
propertyName: "foo",
|
||||
inner: &configurableInner[string]{
|
||||
single: singleConfigurable[string]{
|
||||
cases: []ConfigurableCase[string]{{
|
||||
value: StringPtr("bar"),
|
||||
}},
|
||||
Foo: newConfigurableWithPropertyName(
|
||||
"foo",
|
||||
nil,
|
||||
[]ConfigurableCase[string]{{
|
||||
value: &parser.String{
|
||||
LiteralPos: scanner.Position{
|
||||
Offset: 17,
|
||||
Line: 3,
|
||||
Column: 10,
|
||||
},
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
false,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -756,16 +762,22 @@ var validUnpackTestCases = []struct {
|
|||
&struct {
|
||||
Foo Configurable[bool]
|
||||
}{
|
||||
Foo: Configurable[bool]{
|
||||
propertyName: "foo",
|
||||
inner: &configurableInner[bool]{
|
||||
single: singleConfigurable[bool]{
|
||||
cases: []ConfigurableCase[bool]{{
|
||||
value: BoolPtr(true),
|
||||
}},
|
||||
Foo: newConfigurableWithPropertyName(
|
||||
"foo",
|
||||
nil,
|
||||
[]ConfigurableCase[bool]{{
|
||||
value: &parser.Bool{
|
||||
LiteralPos: scanner.Position{
|
||||
Offset: 17,
|
||||
Line: 3,
|
||||
Column: 10,
|
||||
},
|
||||
Value: true,
|
||||
Token: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
false,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -780,16 +792,43 @@ var validUnpackTestCases = []struct {
|
|||
&struct {
|
||||
Foo Configurable[[]string]
|
||||
}{
|
||||
Foo: Configurable[[]string]{
|
||||
propertyName: "foo",
|
||||
inner: &configurableInner[[]string]{
|
||||
single: singleConfigurable[[]string]{
|
||||
cases: []ConfigurableCase[[]string]{{
|
||||
value: &[]string{"a", "b"},
|
||||
}},
|
||||
Foo: newConfigurableWithPropertyName(
|
||||
"foo",
|
||||
nil,
|
||||
[]ConfigurableCase[[]string]{{
|
||||
value: &parser.List{
|
||||
LBracePos: scanner.Position{
|
||||
Offset: 17,
|
||||
Line: 3,
|
||||
Column: 10,
|
||||
},
|
||||
RBracePos: scanner.Position{
|
||||
Offset: 26,
|
||||
Line: 3,
|
||||
Column: 19,
|
||||
},
|
||||
Values: []parser.Expression{
|
||||
&parser.String{
|
||||
LiteralPos: scanner.Position{
|
||||
Offset: 18,
|
||||
Line: 3,
|
||||
Column: 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 {
|
||||
Foo Configurable[string]
|
||||
}{
|
||||
Foo: Configurable[string]{
|
||||
propertyName: "foo",
|
||||
inner: &configurableInner[string]{
|
||||
single: singleConfigurable[string]{
|
||||
conditions: []ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"my_variable",
|
||||
},
|
||||
Foo: newConfigurableWithPropertyName(
|
||||
"foo",
|
||||
[]ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"my_variable",
|
||||
},
|
||||
}},
|
||||
[]ConfigurableCase[string]{
|
||||
{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "a",
|
||||
}},
|
||||
cases: []ConfigurableCase[string]{
|
||||
{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "a",
|
||||
}},
|
||||
value: StringPtr("a2"),
|
||||
value: &parser.String{
|
||||
LiteralPos: scanner.Position{
|
||||
Offset: 90,
|
||||
Line: 4,
|
||||
Column: 11,
|
||||
},
|
||||
{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "b",
|
||||
}},
|
||||
value: StringPtr("b2"),
|
||||
Value: "a2",
|
||||
},
|
||||
},
|
||||
{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "b",
|
||||
}},
|
||||
value: &parser.String{
|
||||
LiteralPos: scanner.Position{
|
||||
Offset: 106,
|
||||
Line: 5,
|
||||
Column: 11,
|
||||
},
|
||||
{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeDefault,
|
||||
}},
|
||||
value: StringPtr("c2"),
|
||||
Value: "b2",
|
||||
},
|
||||
},
|
||||
{
|
||||
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 {
|
||||
Foo Configurable[string]
|
||||
}{
|
||||
Foo: Configurable[string]{
|
||||
propertyName: "foo",
|
||||
inner: &configurableInner[string]{
|
||||
single: singleConfigurable[string]{
|
||||
conditions: []ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"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"),
|
||||
},
|
||||
Foo: func() Configurable[string] {
|
||||
result := newConfigurableWithPropertyName(
|
||||
"foo",
|
||||
[]ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"my_variable",
|
||||
},
|
||||
},
|
||||
next: &configurableInner[string]{
|
||||
single: singleConfigurable[string]{
|
||||
conditions: []ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"my_2nd_variable",
|
||||
},
|
||||
}},
|
||||
[]ConfigurableCase[string]{
|
||||
{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "a",
|
||||
}},
|
||||
cases: []ConfigurableCase[string]{
|
||||
{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "d",
|
||||
}},
|
||||
value: StringPtr("d2"),
|
||||
value: &parser.String{
|
||||
LiteralPos: scanner.Position{
|
||||
Offset: 90,
|
||||
Line: 4,
|
||||
Column: 11,
|
||||
},
|
||||
{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "e",
|
||||
}},
|
||||
value: StringPtr("e2"),
|
||||
Value: "a2",
|
||||
},
|
||||
},
|
||||
{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "b",
|
||||
}},
|
||||
value: &parser.String{
|
||||
LiteralPos: scanner.Position{
|
||||
Offset: 106,
|
||||
Line: 5,
|
||||
Column: 11,
|
||||
},
|
||||
{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeDefault,
|
||||
}},
|
||||
value: StringPtr("f2"),
|
||||
Value: "b2",
|
||||
},
|
||||
},
|
||||
{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeDefault,
|
||||
}},
|
||||
value: &parser.String{
|
||||
LiteralPos: scanner.Position{
|
||||
Offset: 126,
|
||||
Line: 6,
|
||||
Column: 15,
|
||||
},
|
||||
Value: "c2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
)
|
||||
result.Append(newConfigurableWithPropertyName(
|
||||
"",
|
||||
[]ConfigurableCondition{{
|
||||
functionName: "soong_config_variable",
|
||||
args: []string{
|
||||
"my_namespace",
|
||||
"my_2nd_variable",
|
||||
},
|
||||
}},
|
||||
[]ConfigurableCase[string]{
|
||||
{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "d",
|
||||
}},
|
||||
value: &parser.String{
|
||||
LiteralPos: scanner.Position{
|
||||
Offset: 218,
|
||||
Line: 8,
|
||||
Column: 11,
|
||||
},
|
||||
Value: "d2",
|
||||
},
|
||||
},
|
||||
{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeString,
|
||||
stringValue: "e",
|
||||
}},
|
||||
value: &parser.String{
|
||||
LiteralPos: scanner.Position{
|
||||
Offset: 234,
|
||||
Line: 9,
|
||||
Column: 11,
|
||||
},
|
||||
Value: "e2",
|
||||
},
|
||||
},
|
||||
{
|
||||
patterns: []ConfigurablePattern{{
|
||||
typ: configurablePatternTypeDefault,
|
||||
}},
|
||||
value: &parser.String{
|
||||
LiteralPos: scanner.Position{
|
||||
Offset: 254,
|
||||
Line: 10,
|
||||
Column: 15,
|
||||
},
|
||||
Value: "f2",
|
||||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
))
|
||||
return result
|
||||
}(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -953,26 +1052,37 @@ var validUnpackTestCases = []struct {
|
|||
Foo Configurable[string]
|
||||
Bar Configurable[bool]
|
||||
}{
|
||||
Foo: Configurable[string]{
|
||||
propertyName: "foo",
|
||||
inner: &configurableInner[string]{
|
||||
single: singleConfigurable[string]{
|
||||
cases: []ConfigurableCase[string]{{
|
||||
value: StringPtr("asdf"),
|
||||
}},
|
||||
Foo: newConfigurableWithPropertyName(
|
||||
"foo",
|
||||
nil,
|
||||
[]ConfigurableCase[string]{{
|
||||
value: &parser.String{
|
||||
LiteralPos: scanner.Position{
|
||||
Offset: 25,
|
||||
Line: 2,
|
||||
Column: 25,
|
||||
},
|
||||
Value: "asdf",
|
||||
},
|
||||
},
|
||||
},
|
||||
Bar: Configurable[bool]{
|
||||
propertyName: "bar",
|
||||
inner: &configurableInner[bool]{
|
||||
single: singleConfigurable[bool]{
|
||||
cases: []ConfigurableCase[bool]{{
|
||||
value: BoolPtr(true),
|
||||
}},
|
||||
}},
|
||||
false,
|
||||
),
|
||||
Bar: newConfigurableWithPropertyName(
|
||||
"bar",
|
||||
nil,
|
||||
[]ConfigurableCase[bool]{{
|
||||
value: &parser.Bool{
|
||||
LiteralPos: scanner.Position{
|
||||
Offset: 54,
|
||||
Line: 3,
|
||||
Column: 23,
|
||||
},
|
||||
Value: true,
|
||||
Token: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
false,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue