platform_build_blueprint/parser/parser.go
Cole Faust 6437d4e737 Select statements
Select statements are a new blueprint feature inspired by bazel's select
statements. They are essentially alternative syntax for soong config
variables that require less boilerplate. In addition, they support
making decisions based on a module's variant, which will eliminate
the need for manual property struct manipulation, such as the arch
mutator's arch: and target: properties.

In order to support decisions based on the variant, select statements
cannot be evaluated as soon as they're parsed. Instead, they must be
stored in the property struct unevaluated. This means that individual
properties need to change their type from say, string, to
Configurable[string]. Currently, only configurable strings, bools, and
string slices are supported, but more types can be added later.
The module implementation must call my_property.Evaluate(ctx) in order
to get the final, resolved value of the select statement.

Bug: 323382414
Test: go tests
Change-Id: I62f8721d7f0ac3d1df4a06d7eaa260a5aa7fcba3
2024-03-06 15:00:39 -08:00

818 lines
16 KiB
Go

// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package parser
import (
"errors"
"fmt"
"io"
"sort"
"strconv"
"strings"
"text/scanner"
)
var errTooManyErrors = errors.New("too many errors")
const maxErrors = 1
type ParseError struct {
Err error
Pos scanner.Position
}
func (e *ParseError) Error() string {
return fmt.Sprintf("%s: %s", e.Pos, e.Err)
}
type File struct {
Name string
Defs []Definition
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 {
if r == errTooManyErrors {
errs = p.errors
return
}
panic(r)
}
}()
p.next()
defs := p.parseDefinitions()
p.accept(scanner.EOF)
errs = p.errors
comments := p.comments
return &File{
Name: p.scanner.Filename,
Defs: defs,
Comments: comments,
}, errs
}
func ParseAndEval(filename string, r io.Reader, scope *Scope) (file *File, errs []error) {
p := newParser(r, scope)
p.eval = true
p.scanner.Filename = filename
return parse(p)
}
func Parse(filename string, r io.Reader, scope *Scope) (file *File, errs []error) {
p := newParser(r, scope)
p.scanner.Filename = filename
return parse(p)
}
func ParseExpression(r io.Reader) (value Expression, errs []error) {
p := newParser(r, NewScope(nil))
p.next()
value = p.parseExpression()
p.accept(scanner.EOF)
errs = p.errors
return
}
type parser struct {
scanner scanner.Scanner
tok rune
errors []error
scope *Scope
comments []*CommentGroup
eval bool
}
func newParser(r io.Reader, scope *Scope) *parser {
p := &parser{}
p.scope = scope
p.scanner.Init(r)
p.scanner.Error = func(sc *scanner.Scanner, msg string) {
p.errorf(msg)
}
p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanStrings |
scanner.ScanRawStrings | scanner.ScanComments
return p
}
func (p *parser) error(err error) {
pos := p.scanner.Position
if !pos.IsValid() {
pos = p.scanner.Pos()
}
err = &ParseError{
Err: err,
Pos: pos,
}
p.errors = append(p.errors, err)
if len(p.errors) >= maxErrors {
panic(errTooManyErrors)
}
}
func (p *parser) errorf(format string, args ...interface{}) {
p.error(fmt.Errorf(format, args...))
}
func (p *parser) accept(toks ...rune) bool {
for _, tok := range toks {
if p.tok != tok {
p.errorf("expected %s, found %s", scanner.TokenString(tok),
scanner.TokenString(p.tok))
return false
}
p.next()
}
return true
}
func (p *parser) next() {
if p.tok != scanner.EOF {
p.tok = p.scanner.Scan()
if p.tok == scanner.Comment {
var comments []*Comment
for p.tok == scanner.Comment {
lines := strings.Split(p.scanner.TokenText(), "\n")
if len(comments) > 0 && p.scanner.Position.Line > comments[len(comments)-1].End().Line+1 {
p.comments = append(p.comments, &CommentGroup{Comments: comments})
comments = nil
}
comments = append(comments, &Comment{lines, p.scanner.Position})
p.tok = p.scanner.Scan()
}
p.comments = append(p.comments, &CommentGroup{Comments: comments})
}
}
return
}
func (p *parser) parseDefinitions() (defs []Definition) {
for {
switch p.tok {
case scanner.Ident:
ident := p.scanner.TokenText()
pos := p.scanner.Position
p.accept(scanner.Ident)
switch p.tok {
case '+':
p.accept('+')
defs = append(defs, p.parseAssignment(ident, pos, "+="))
case '=':
defs = append(defs, p.parseAssignment(ident, pos, "="))
case '{', '(':
defs = append(defs, p.parseModule(ident, pos))
default:
p.errorf("expected \"=\" or \"+=\" or \"{\" or \"(\", found %s",
scanner.TokenString(p.tok))
}
case scanner.EOF:
return
default:
p.errorf("expected assignment or module definition, found %s",
scanner.TokenString(p.tok))
return
}
}
}
func (p *parser) parseAssignment(name string, namePos scanner.Position,
assigner string) (assignment *Assignment) {
assignment = new(Assignment)
pos := p.scanner.Position
if !p.accept('=') {
return
}
value := p.parseExpression()
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
}
func (p *parser) parseModule(typ string, typPos scanner.Position) *Module {
compat := false
lbracePos := p.scanner.Position
if p.tok == '{' {
compat = true
}
if !p.accept(p.tok) {
return nil
}
properties := p.parsePropertyList(true, compat)
rbracePos := p.scanner.Position
if !compat {
p.accept(')')
} else {
p.accept('}')
}
return &Module{
Type: typ,
TypePos: typPos,
Map: Map{
Properties: properties,
LBracePos: lbracePos,
RBracePos: rbracePos,
},
}
}
func (p *parser) parsePropertyList(isModule, compat bool) (properties []*Property) {
for p.tok == scanner.Ident {
property := p.parseProperty(isModule, compat)
properties = append(properties, property)
if p.tok != ',' {
// There was no comma, so the list is done.
break
}
p.accept(',')
}
return
}
func (p *parser) parseProperty(isModule, compat bool) (property *Property) {
property = new(Property)
name := p.scanner.TokenText()
namePos := p.scanner.Position
p.accept(scanner.Ident)
pos := p.scanner.Position
if isModule {
if compat {
if !p.accept(':') {
return
}
} else {
if !p.accept('=') {
return
}
}
} else {
if !p.accept(':') {
return
}
}
value := p.parseExpression()
property.Name = name
property.NamePos = namePos
property.Value = value
property.ColonPos = pos
return
}
func (p *parser) parseExpression() (value Expression) {
value = p.parseValue()
switch p.tok {
case '+':
return p.parseOperator(value)
case '-':
p.errorf("subtraction not supported: %s", p.scanner.String())
return value
default:
return value
}
}
func (p *parser) evaluateOperator(value1, value2 Expression, operator rune,
pos scanner.Position) (*Operator, error) {
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{
Typ: SelectTypeUnconfigured,
Cases: []*SelectCase{{
Pattern: String{
Value: "__soong_conditions_default__",
},
Value: e1,
}},
}
}
}
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) *Operator {
operator := p.tok
pos := p.scanner.Position
p.accept(operator)
value2 := p.parseExpression()
value, err := p.evaluateOperator(value1, value2, operator, pos)
if err != nil {
p.error(err)
return nil
}
return value
}
func (p *parser) parseValue() (value Expression) {
switch p.tok {
case scanner.Ident:
switch text := p.scanner.TokenText(); text {
case "true", "false":
return p.parseBoolean()
case "select":
return p.parseSelect()
default:
return p.parseVariable()
}
case '-', scanner.Int: // Integer might have '-' sign ahead ('+' is only treated as operator now)
return p.parseIntValue()
case scanner.String, scanner.RawString:
return p.parseStringValue()
case '[':
return p.parseListValue()
case '{':
return p.parseMapValue()
default:
p.errorf("expected bool, list, or string value; found %s",
scanner.TokenString(p.tok))
return
}
}
func (p *parser) parseBoolean() Expression {
switch text := p.scanner.TokenText(); text {
case "true", "false":
result := &Bool{
LiteralPos: p.scanner.Position,
Value: text == "true",
Token: text,
}
p.accept(scanner.Ident)
return result
default:
p.errorf("Expected true/false, got %q", text)
return nil
}
}
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)
return value
}
func (p *parser) parseSelect() Expression {
result := &Select{
KeywordPos: p.scanner.Position,
}
p.accept(scanner.Ident)
if !p.accept('(') {
return nil
}
switch p.scanner.TokenText() {
case "release_variable":
result.Typ = SelectTypeReleaseVariable
case "soong_config_variable":
result.Typ = SelectTypeSoongConfigVariable
case "product_variable":
result.Typ = SelectTypeProductVariable
case "variant":
result.Typ = SelectTypeVariant
default:
p.errorf("unknown select type %q, expected release_variable, soong_config_variable, product_variable, or variant", p.scanner.TokenText())
return nil
}
p.accept(scanner.Ident)
if !p.accept('(') {
return nil
}
if s := p.parseStringValue(); s != nil {
result.Condition = *s
} else {
return nil
}
if result.Typ == SelectTypeSoongConfigVariable {
if !p.accept(',') {
return nil
}
if s := p.parseStringValue(); s != nil {
result.Condition.Value += ":" + s.Value
} else {
return nil
}
}
if !p.accept(')') {
return nil
}
if !p.accept(',') {
return nil
}
result.LBracePos = p.scanner.Position
if !p.accept('{') {
return nil
}
for p.tok == scanner.String {
c := &SelectCase{}
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
}
c.Pattern = *s
} else {
return nil
}
c.ColonPos = p.scanner.Position
if !p.accept(':') {
return nil
}
c.Value = p.parseExpression()
if !p.accept(',') {
return nil
}
result.Cases = append(result.Cases, c)
}
// Default must be last
if p.tok == scanner.Ident {
if p.scanner.TokenText() != "_" {
p.errorf("select cases can either be quoted strings or '_' to match any value")
return nil
}
c := &SelectCase{Pattern: String{
LiteralPos: p.scanner.Position,
Value: "__soong_conditions_default__",
}}
p.accept(scanner.Ident)
c.ColonPos = p.scanner.Position
if !p.accept(':') {
return nil
}
c.Value = p.parseExpression()
if !p.accept(',') {
return nil
}
result.Cases = append(result.Cases, c)
}
result.RBracePos = p.scanner.Position
if !p.accept('}') {
return nil
}
if !p.accept(')') {
return nil
}
return result
}
func (p *parser) parseStringValue() *String {
str, err := strconv.Unquote(p.scanner.TokenText())
if err != nil {
p.errorf("couldn't parse string: %s", err)
return nil
}
value := &String{
LiteralPos: p.scanner.Position,
Value: str,
}
p.accept(p.tok)
return value
}
func (p *parser) parseIntValue() *Int64 {
var str string
literalPos := p.scanner.Position
if p.tok == '-' {
str += string(p.tok)
p.accept(p.tok)
if p.tok != scanner.Int {
p.errorf("expected int; found %s", scanner.TokenString(p.tok))
return nil
}
}
str += p.scanner.TokenText()
i, err := strconv.ParseInt(str, 10, 64)
if err != nil {
p.errorf("couldn't parse int: %s", err)
return nil
}
value := &Int64{
LiteralPos: literalPos,
Value: i,
Token: str,
}
p.accept(scanner.Int)
return value
}
func (p *parser) parseListValue() *List {
lBracePos := p.scanner.Position
if !p.accept('[') {
return nil
}
var elements []Expression
for p.tok != ']' {
element := p.parseExpression()
elements = append(elements, element)
if p.tok != ',' {
// There was no comma, so the list is done.
break
}
p.accept(',')
}
rBracePos := p.scanner.Position
p.accept(']')
return &List{
LBracePos: lBracePos,
RBracePos: rBracePos,
Values: elements,
}
}
func (p *parser) parseMapValue() *Map {
lBracePos := p.scanner.Position
if !p.accept('{') {
return nil
}
properties := p.parsePropertyList(false, false)
rBracePos := p.scanner.Position
p.accept('}')
return &Map{
LBracePos: lBracePos,
RBracePos: rBracePos,
Properties: properties,
}
}
type Scope struct {
vars map[string]*Assignment
inheritedVars map[string]*Assignment
}
func NewScope(s *Scope) *Scope {
newScope := &Scope{
vars: make(map[string]*Assignment),
inheritedVars: make(map[string]*Assignment),
}
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)
}
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, bool) {
if a, ok := s.vars[name]; ok {
return a, true
}
if a, ok := s.inheritedVars[name]; ok {
return a, false
}
return nil, false
}
func (s *Scope) String() string {
vars := []string{}
for k := range s.vars {
vars = append(vars, k)
}
for k := range s.inheritedVars {
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())
}
}
return strings.Join(ret, "\n")
}