Files
build_soong/androidmk/parser/parser.go
Sasha Smundak 9c35d8bfde Parse else ifxxx statement
More recent versions of make provide "else ifdef/ifndef/ifeq/ifneq" directive:
ifdef FOO
...
else ifdef BAR
...
endif
Fix the parser to handle it. It returns the same Directive as the respective ifxxx
counterpart, only that the Name is set to "elifdef/elifndef/elifeq/elifneq".

Test: treehugger
Change-Id: I74c6a2c7224bce1dd3465012fa84880fae21349b
2020-11-17 23:18:14 -08:00

661 lines
13 KiB
Go

// Copyright 2017 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"
"text/scanner"
)
var errTooManyErrors = errors.New("too many errors")
const maxErrors = 100
type ParseError struct {
Err error
Pos scanner.Position
}
func (e *ParseError) Error() string {
return fmt.Sprintf("%s: %s", e.Pos, e.Err)
}
const builtinDollar = "__builtin_dollar"
var builtinDollarName = SimpleMakeString(builtinDollar, NoPos)
func (p *parser) Parse() ([]Node, []error) {
defer func() {
if r := recover(); r != nil {
if r == errTooManyErrors {
return
}
panic(r)
}
}()
p.parseLines()
p.accept(scanner.EOF)
p.nodes = append(p.nodes, p.comments...)
sort.Sort(byPosition(p.nodes))
return p.nodes, p.errors
}
type parser struct {
scanner scanner.Scanner
tok rune
errors []error
comments []Node
nodes []Node
lines []int
}
func NewParser(filename string, r io.Reader) *parser {
p := &parser{}
p.lines = []int{0}
p.scanner.Init(r)
p.scanner.Error = func(sc *scanner.Scanner, msg string) {
p.errorf(msg)
}
p.scanner.Whitespace = 0
p.scanner.IsIdentRune = func(ch rune, i int) bool {
return ch > 0 && ch != ':' && ch != '#' && ch != '=' && ch != '+' && ch != '$' &&
ch != '\\' && ch != '(' && ch != ')' && ch != '{' && ch != '}' && ch != ';' &&
ch != '|' && ch != '?' && ch != '\r' && !isWhitespace(ch)
}
p.scanner.Mode = scanner.ScanIdents
p.scanner.Filename = filename
p.next()
return p
}
func (p *parser) Unpack(pos Pos) scanner.Position {
offset := int(pos)
line := sort.Search(len(p.lines), func(i int) bool { return p.lines[i] > offset }) - 1
return scanner.Position{
Filename: p.scanner.Filename,
Line: line + 1,
Column: offset - p.lines[line] + 1,
Offset: offset,
}
}
func (p *parser) pos() Pos {
pos := p.scanner.Position
if !pos.IsValid() {
pos = p.scanner.Pos()
}
return Pos(pos.Offset)
}
func (p *parser) errorf(format string, args ...interface{}) {
err := &ParseError{
Err: fmt.Errorf(format, args...),
Pos: p.scanner.Position,
}
p.errors = append(p.errors, err)
if len(p.errors) >= maxErrors {
panic(errTooManyErrors)
}
}
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()
for p.tok == '\r' {
p.tok = p.scanner.Scan()
}
}
if p.tok == '\n' {
p.lines = append(p.lines, p.scanner.Position.Offset+1)
}
}
func (p *parser) parseLines() {
for {
p.ignoreWhitespace()
if p.parseDirective() {
continue
}
ident := p.parseExpression('=', '?', ':', '#', '\n')
p.ignoreSpaces()
switch p.tok {
case '?':
p.accept('?')
if p.tok == '=' {
p.parseAssignment("?=", nil, ident)
} else {
p.errorf("expected = after ?")
}
case '+':
p.accept('+')
if p.tok == '=' {
p.parseAssignment("+=", nil, ident)
} else {
p.errorf("expected = after +")
}
case ':':
p.accept(':')
switch p.tok {
case '=':
p.parseAssignment(":=", nil, ident)
default:
p.parseRule(ident)
}
case '=':
p.parseAssignment("=", nil, ident)
case '#', '\n', scanner.EOF:
ident.TrimRightSpaces()
if v, ok := toVariable(ident); ok {
p.nodes = append(p.nodes, &v)
} else if !ident.Empty() {
p.errorf("expected directive, rule, or assignment after ident " + ident.Dump())
}
switch p.tok {
case scanner.EOF:
return
case '\n':
p.accept('\n')
case '#':
p.parseComment()
}
default:
p.errorf("expected assignment or rule definition, found %s\n",
p.scanner.TokenText())
return
}
}
}
func (p *parser) parseDirective() bool {
if p.tok != scanner.Ident || !isDirective(p.scanner.TokenText()) {
return false
}
d := p.scanner.TokenText()
pos := p.pos()
p.accept(scanner.Ident)
endPos := NoPos
expression := SimpleMakeString("", pos)
switch d {
case "endif", "endef":
// Nothing
case "else":
p.ignoreSpaces()
if p.tok != '\n' {
d = p.scanner.TokenText()
p.accept(scanner.Ident)
if d == "ifdef" || d == "ifndef" || d == "ifeq" || d == "ifneq" {
d = "el" + d
p.ignoreSpaces()
expression = p.parseExpression()
} else {
p.errorf("expected ifdef/ifndef/ifeq/ifneq, found %s", d)
}
}
case "define":
expression, endPos = p.parseDefine()
default:
p.ignoreSpaces()
expression = p.parseExpression()
}
p.nodes = append(p.nodes, &Directive{
NamePos: pos,
Name: d,
Args: expression,
EndPos: endPos,
})
return true
}
func (p *parser) parseDefine() (*MakeString, Pos) {
value := SimpleMakeString("", p.pos())
loop:
for {
switch p.tok {
case scanner.Ident:
value.appendString(p.scanner.TokenText())
if p.scanner.TokenText() == "endef" {
p.accept(scanner.Ident)
break loop
}
p.accept(scanner.Ident)
case '\\':
p.parseEscape()
switch p.tok {
case '\n':
value.appendString(" ")
case scanner.EOF:
p.errorf("expected escaped character, found %s",
scanner.TokenString(p.tok))
break loop
default:
value.appendString(`\` + string(p.tok))
}
p.accept(p.tok)
//TODO: handle variables inside defines? result depends if
//define is used in make or rule context
//case '$':
// variable := p.parseVariable()
// value.appendVariable(variable)
case scanner.EOF:
p.errorf("unexpected EOF while looking for endef")
break loop
default:
value.appendString(p.scanner.TokenText())
p.accept(p.tok)
}
}
return value, p.pos()
}
func (p *parser) parseEscape() {
p.scanner.Mode = 0
p.accept('\\')
p.scanner.Mode = scanner.ScanIdents
}
func (p *parser) parseExpression(end ...rune) *MakeString {
value := SimpleMakeString("", p.pos())
endParen := false
for _, r := range end {
if r == ')' {
endParen = true
}
}
parens := 0
loop:
for {
if endParen && parens > 0 && p.tok == ')' {
parens--
value.appendString(")")
p.accept(')')
continue
}
for _, r := range end {
if p.tok == r {
break loop
}
}
switch p.tok {
case '\n':
break loop
case scanner.Ident:
value.appendString(p.scanner.TokenText())
p.accept(scanner.Ident)
case '\\':
p.parseEscape()
switch p.tok {
case '\n':
value.appendString(" ")
case scanner.EOF:
p.errorf("expected escaped character, found %s",
scanner.TokenString(p.tok))
return value
default:
value.appendString(`\` + string(p.tok))
}
p.accept(p.tok)
case '#':
p.parseComment()
break loop
case '$':
var variable Variable
variable = p.parseVariable()
if variable.Name == builtinDollarName {
value.appendString("$")
} else {
value.appendVariable(variable)
}
case scanner.EOF:
break loop
case '(':
if endParen {
parens++
}
value.appendString("(")
p.accept('(')
default:
value.appendString(p.scanner.TokenText())
p.accept(p.tok)
}
}
if parens > 0 {
p.errorf("expected closing paren %s", value.Dump())
}
return value
}
func (p *parser) parseVariable() Variable {
pos := p.pos()
p.accept('$')
var name *MakeString
switch p.tok {
case '(':
return p.parseBracketedVariable('(', ')', pos)
case '{':
return p.parseBracketedVariable('{', '}', pos)
case '$':
name = builtinDollarName
p.accept(p.tok)
case scanner.EOF:
p.errorf("expected variable name, found %s",
scanner.TokenString(p.tok))
default:
name = p.parseExpression(variableNameEndRunes...)
}
return p.nameToVariable(name)
}
func (p *parser) parseBracketedVariable(start, end rune, pos Pos) Variable {
p.accept(start)
name := p.parseExpression(end)
p.accept(end)
return p.nameToVariable(name)
}
func (p *parser) nameToVariable(name *MakeString) Variable {
return Variable{
Name: name,
}
}
func (p *parser) parseRule(target *MakeString) {
prerequisites, newLine := p.parseRulePrerequisites(target)
recipe := ""
recipePos := p.pos()
loop:
for {
if newLine {
if p.tok == '\t' {
p.accept('\t')
newLine = false
continue loop
} else if p.parseDirective() {
newLine = false
continue
} else {
break loop
}
}
newLine = false
switch p.tok {
case '\\':
p.parseEscape()
recipe += string(p.tok)
p.accept(p.tok)
case '\n':
newLine = true
recipe += "\n"
p.accept('\n')
case scanner.EOF:
break loop
default:
recipe += p.scanner.TokenText()
p.accept(p.tok)
}
}
if prerequisites != nil {
p.nodes = append(p.nodes, &Rule{
Target: target,
Prerequisites: prerequisites,
Recipe: recipe,
RecipePos: recipePos,
})
}
}
func (p *parser) parseRulePrerequisites(target *MakeString) (*MakeString, bool) {
newLine := false
p.ignoreSpaces()
prerequisites := p.parseExpression('#', '\n', ';', ':', '=')
switch p.tok {
case '\n':
p.accept('\n')
newLine = true
case '#':
p.parseComment()
newLine = true
case ';':
p.accept(';')
case ':':
p.accept(':')
if p.tok == '=' {
p.parseAssignment(":=", target, prerequisites)
return nil, true
} else {
more := p.parseExpression('#', '\n', ';')
prerequisites.appendMakeString(more)
}
case '=':
p.parseAssignment("=", target, prerequisites)
return nil, true
case scanner.EOF:
// do nothing
default:
p.errorf("unexpected token %s after rule prerequisites", scanner.TokenString(p.tok))
}
return prerequisites, newLine
}
func (p *parser) parseComment() {
pos := p.pos()
p.accept('#')
comment := ""
loop:
for {
switch p.tok {
case '\\':
p.parseEscape()
if p.tok == '\n' {
// Special case: '\' does not "escape" newline in comment (b/127521510)
comment += "\\"
p.accept(p.tok)
break loop
}
comment += "\\" + p.scanner.TokenText()
p.accept(p.tok)
case '\n':
p.accept('\n')
break loop
case scanner.EOF:
break loop
default:
comment += p.scanner.TokenText()
p.accept(p.tok)
}
}
p.comments = append(p.comments, &Comment{
CommentPos: pos,
Comment: comment,
})
}
func (p *parser) parseAssignment(t string, target *MakeString, ident *MakeString) {
// The value of an assignment is everything including and after the first
// non-whitespace character after the = until the end of the logical line,
// which may included escaped newlines
p.accept('=')
value := p.parseExpression()
value.TrimLeftSpaces()
if ident.EndsWith('+') && t == "=" {
ident.TrimRightOne()
t = "+="
}
ident.TrimRightSpaces()
p.nodes = append(p.nodes, &Assignment{
Name: ident,
Value: value,
Target: target,
Type: t,
})
}
type androidMkModule struct {
assignments map[string]string
}
type androidMkFile struct {
assignments map[string]string
modules []androidMkModule
includes []string
}
var directives = [...]string{
"define",
"else",
"endef",
"endif",
"ifdef",
"ifeq",
"ifndef",
"ifneq",
"include",
"-include",
}
var functions = [...]string{
"abspath",
"addprefix",
"addsuffix",
"basename",
"dir",
"notdir",
"subst",
"suffix",
"filter",
"filter-out",
"findstring",
"firstword",
"flavor",
"join",
"lastword",
"patsubst",
"realpath",
"shell",
"sort",
"strip",
"wildcard",
"word",
"wordlist",
"words",
"origin",
"foreach",
"call",
"info",
"error",
"warning",
"if",
"or",
"and",
"value",
"eval",
"file",
}
func init() {
sort.Strings(directives[:])
sort.Strings(functions[:])
}
func isDirective(s string) bool {
for _, d := range directives {
if s == d {
return true
} else if s < d {
return false
}
}
return false
}
func isFunctionName(s string) bool {
for _, f := range functions {
if s == f {
return true
} else if s < f {
return false
}
}
return false
}
func isWhitespace(ch rune) bool {
return ch == ' ' || ch == '\t' || ch == '\n'
}
func isValidVariableRune(ch rune) bool {
return ch != scanner.Ident && ch != ':' && ch != '=' && ch != '#'
}
var whitespaceRunes = []rune{' ', '\t', '\n'}
var variableNameEndRunes = append([]rune{':', '=', '#', ')', '}'}, whitespaceRunes...)
func (p *parser) ignoreSpaces() int {
skipped := 0
for p.tok == ' ' || p.tok == '\t' {
p.accept(p.tok)
skipped++
}
return skipped
}
func (p *parser) ignoreWhitespace() {
for isWhitespace(p.tok) {
p.accept(p.tok)
}
}