| // 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 |
| |
| const default_select_branch_name = "__soong_conditions_default__" |
| const any_select_branch_name = "__soong_conditions_any__" |
| |
| 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 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) { |
| file, errs = Parse(filename, r) |
| if len(errs) > 0 { |
| return nil, errs |
| } |
| |
| // 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) (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) |
| p.next() |
| value = p.parseExpression() |
| p.accept(scanner.EOF) |
| errs = p.errors |
| return |
| } |
| |
| type parser struct { |
| scanner scanner.Scanner |
| tok rune |
| errors []error |
| comments []*CommentGroup |
| } |
| |
| func newParser(r io.Reader) *parser { |
| p := &parser{} |
| 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}) |
| } |
| } |
| } |
| |
| 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) { |
| |
| // These are used as keywords in select statements, prevent making variables |
| // with the same name to avoid any confusion. |
| switch name { |
| case "default", "unset": |
| p.errorf("'default' and 'unset' are reserved keywords, and cannot be used as variable names") |
| return nil |
| } |
| |
| assignment = new(Assignment) |
| |
| pos := p.scanner.Position |
| if !p.accept('=') { |
| return |
| } |
| value := p.parseExpression() |
| |
| assignment.Name = name |
| assignment.NamePos = namePos |
| assignment.Value = value |
| assignment.EqualsPos = pos |
| assignment.Assigner = assigner |
| |
| 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 { |
| properties = append(properties, p.parseProperty(isModule, compat)) |
| |
| 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) parseOperator(value1 Expression) Expression { |
| operator := p.tok |
| pos := p.scanner.Position |
| p.accept(operator) |
| |
| value2 := p.parseExpression() |
| |
| return &Operator{ |
| Args: [2]Expression{value1, value2}, |
| Operator: operator, |
| OperatorPos: pos, |
| } |
| } |
| |
| 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() |
| value = &Variable{ |
| Name: text, |
| NamePos: p.scanner.Position, |
| } |
| |
| p.accept(scanner.Ident) |
| return value |
| } |
| |
| func (p *parser) parseSelect() Expression { |
| result := &Select{ |
| KeywordPos: p.scanner.Position, |
| } |
| // Read the "select(" |
| p.accept(scanner.Ident) |
| if !p.accept('(') { |
| return nil |
| } |
| |
| // If we see another '(', there's probably multiple conditions and there must |
| // be a ')' after. Set the multipleConditions variable to remind us to check for |
| // the ')' after. |
| multipleConditions := false |
| if p.tok == '(' { |
| multipleConditions = true |
| p.accept('(') |
| } |
| |
| // Read all individual conditions |
| conditions := []ConfigurableCondition{} |
| for first := true; first || multipleConditions; first = false { |
| condition := ConfigurableCondition{ |
| position: p.scanner.Position, |
| FunctionName: p.scanner.TokenText(), |
| } |
| if !p.accept(scanner.Ident) { |
| return nil |
| } |
| if !p.accept('(') { |
| return nil |
| } |
| |
| for p.tok != ')' { |
| if s := p.parseStringValue(); s != nil { |
| condition.Args = append(condition.Args, *s) |
| } else { |
| return nil |
| } |
| if p.tok == ')' { |
| break |
| } |
| if !p.accept(',') { |
| return nil |
| } |
| } |
| p.accept(')') |
| |
| for _, c := range conditions { |
| if c.Equals(condition) { |
| p.errorf("Duplicate select condition found: %s", c.String()) |
| } |
| } |
| |
| conditions = append(conditions, condition) |
| |
| if multipleConditions { |
| if p.tok == ')' { |
| p.next() |
| break |
| } |
| if !p.accept(',') { |
| return nil |
| } |
| // Retry the closing parent to allow for a trailing comma |
| if p.tok == ')' { |
| p.next() |
| break |
| } |
| } |
| } |
| |
| if multipleConditions && len(conditions) < 2 { |
| p.errorf("Expected multiple select conditions due to the extra parenthesis, but only found 1. Please remove the extra parenthesis.") |
| return nil |
| } |
| |
| result.Conditions = conditions |
| |
| if !p.accept(',') { |
| return nil |
| } |
| |
| result.LBracePos = p.scanner.Position |
| if !p.accept('{') { |
| return nil |
| } |
| |
| 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 "any": |
| result.Value = &String{ |
| LiteralPos: p.scanner.Position, |
| Value: any_select_branch_name, |
| } |
| p.next() |
| if binding, exists := maybeParseBinding(); exists { |
| result.Binding = binding |
| } |
| return result |
| case "default": |
| result.Value = &String{ |
| LiteralPos: p.scanner.Position, |
| Value: default_select_branch_name, |
| } |
| p.next() |
| return result |
| case "true": |
| result.Value = &Bool{ |
| LiteralPos: p.scanner.Position, |
| Value: true, |
| } |
| p.next() |
| return result |
| case "false": |
| result.Value = &Bool{ |
| LiteralPos: p.scanner.Position, |
| Value: false, |
| } |
| p.next() |
| return result |
| default: |
| p.errorf("Expected a string, true, false, or default, got %s", p.scanner.TokenText()) |
| } |
| case scanner.Int: |
| if i := p.parseIntValue(); i != nil { |
| result.Value = i |
| return result |
| } |
| p.errorf("Expected a string, int, 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 patterns starting with __soong are reserved for internal use") |
| return result |
| } |
| result.Value = s |
| return result |
| } |
| fallthrough |
| default: |
| p.errorf("Expected a string, int, true, false, or default, got %s", p.scanner.TokenText()) |
| } |
| return result |
| } |
| |
| hasNonUnsetValue := false |
| for p.tok != '}' { |
| c := &SelectCase{} |
| |
| if multipleConditions { |
| if !p.accept('(') { |
| return nil |
| } |
| for i := 0; i < len(conditions); i++ { |
| c.Patterns = append(c.Patterns, parseOnePattern()) |
| if i < len(conditions)-1 { |
| if !p.accept(',') { |
| return nil |
| } |
| } else if p.tok == ',' { |
| // allow optional trailing comma |
| p.next() |
| } |
| } |
| if !p.accept(')') { |
| return nil |
| } |
| } else { |
| 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} |
| p.accept(scanner.Ident) |
| } else { |
| hasNonUnsetValue = true |
| c.Value = p.parseExpression() |
| } |
| // allow trailing comma, require it if not seeing a } |
| if p.tok != '}' { |
| if !p.accept(',') { |
| return nil |
| } |
| } |
| result.Cases = append(result.Cases, c) |
| } |
| |
| // If all branches have the value "unset", then this is equivalent |
| // to an empty select. |
| if !hasNonUnsetValue { |
| p.errorf("This select statement is empty, remove it") |
| return nil |
| } |
| |
| 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.Value.(*String); ok { |
| return a2.Value == b2.Value |
| } else { |
| return false |
| } |
| case *Bool: |
| if b2, ok := b.Value.(*Bool); ok { |
| return a2.Value == b2.Value |
| } else { |
| return false |
| } |
| case *Int64: |
| if b2, ok := b.Value.(*Int64); ok { |
| return a2.Value == b2.Value |
| } else { |
| return false |
| } |
| default: |
| // true so that we produce an error in this unexpected scenario |
| return true |
| } |
| } |
| |
| patternListsEqual := func(a, b []SelectPattern) bool { |
| if len(a) != len(b) { |
| return false |
| } |
| for i := range a { |
| if !patternsEqual(a[i], b[i]) { |
| return false |
| } |
| } |
| return true |
| } |
| |
| for i, c := range result.Cases { |
| // 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.Value.(*String); !ok || x2.Value != default_select_branch_name { |
| isAllDefault = false |
| break |
| } |
| } |
| if isAllDefault { |
| p.errorf("Found a default select branch at index %d, expected it to be last (index %d)", i, len(result.Cases)-1) |
| return nil |
| } |
| } |
| } |
| |
| 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 |
| preventInheriting map[string]bool |
| parentScope *Scope |
| } |
| |
| func NewScope(s *Scope) *Scope { |
| return &Scope{ |
| vars: make(map[string]*Assignment), |
| preventInheriting: make(map[string]bool), |
| parentScope: s, |
| } |
| } |
| |
| 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) |
| } |
| return nil |
| } |
| |
| 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) GetLocal(name string) *Assignment { |
| if s == nil { |
| return nil |
| } |
| if a, ok := s.vars[name]; ok { |
| return a |
| } |
| return nil |
| } |
| |
| // 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 { |
| var sb strings.Builder |
| s.stringInner(&sb) |
| return sb.String() |
| } |
| |
| func (s *Scope) stringInner(sb *strings.Builder) { |
| if s == nil { |
| return |
| } |
| vars := make([]string, 0, len(s.vars)) |
| for k := range s.vars { |
| vars = append(vars, k) |
| } |
| |
| sort.Strings(vars) |
| |
| for _, v := range vars { |
| sb.WriteString(s.vars[v].String()) |
| sb.WriteRune('\n') |
| } |
| |
| s.parentScope.stringInner(sb) |
| } |