diff options
author | 2015-01-30 17:27:36 -0800 | |
---|---|---|
committer | 2015-03-13 20:28:16 -0700 | |
commit | 3f40fa460d85b10646d383a3b6b01ea6d569b01b (patch) | |
tree | 542d913a3f0f818042b503948869818a77e99ebc /androidmk/parser/parser.go | |
parent | e441b9df9a68595d0dd7b8ed184aecb27c86054b (diff) |
Add soong_build primary builder
Initial build logic for building android with soong. It can build
a variety of C and C++ files for arm/arm64 and host.
Change-Id: I10eb37c2c2a50be6af1bb5fd568c0962b9476bf0
Diffstat (limited to 'androidmk/parser/parser.go')
-rw-r--r-- | androidmk/parser/parser.go | 633 |
1 files changed, 633 insertions, 0 deletions
diff --git a/androidmk/parser/parser.go b/androidmk/parser/parser.go new file mode 100644 index 000000000..58e612eaa --- /dev/null +++ b/androidmk/parser/parser.go @@ -0,0 +1,633 @@ +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) +} + +func (p *parser) Parse() ([]MakeThing, []error) { + defer func() { + if r := recover(); r != nil { + if r == errTooManyErrors { + return + } + panic(r) + } + }() + + p.parseLines() + p.accept(scanner.EOF) + p.things = append(p.things, p.comments...) + sort.Sort(byPosition(p.things)) + + return p.things, p.errors +} + +type parser struct { + scanner scanner.Scanner + tok rune + errors []error + comments []MakeThing + things []MakeThing +} + +func NewParser(filename string, r io.Reader) *parser { + p := &parser{} + 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) errorf(format string, args ...interface{}) { + pos := p.scanner.Position + if !pos.IsValid() { + pos = p.scanner.Pos() + } + err := &ParseError{ + Err: fmt.Errorf(format, args...), + Pos: pos, + } + 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() + } + } + return +} + +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.things = append(p.things, 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.scanner.Position + endPos := pos + p.accept(scanner.Ident) + + expression := SimpleMakeString("", pos) + + switch d { + case "endif", "endef", "else": + // Nothing + case "define": + expression = p.parseDefine() + default: + p.ignoreSpaces() + expression, endPos = p.parseExpression() + } + + p.things = append(p.things, Directive{ + makeThing: makeThing{ + pos: pos, + endPos: endPos, + }, + Name: d, + Args: expression, + }) + return true +} + +func (p *parser) parseDefine() *MakeString { + value := SimpleMakeString("", p.scanner.Position) + +loop: + for { + switch p.tok { + case scanner.Ident: + if p.scanner.TokenText() == "endef" { + p.accept(scanner.Ident) + break loop + } + 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)) + 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 +} + +func (p *parser) parseEscape() { + p.scanner.Mode = 0 + p.accept('\\') + p.scanner.Mode = scanner.ScanIdents +} + +func (p *parser) parseExpression(end ...rune) (*MakeString, scanner.Position) { + value := SimpleMakeString("", p.scanner.Position) + + endParen := false + for _, r := range end { + if r == ')' { + endParen = true + } + } + parens := 0 + + endPos := p.scanner.Position + +loop: + for { + if endParen && parens > 0 && p.tok == ')' { + parens-- + value.appendString(")") + endPos = p.scanner.Position + 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()) + endPos = p.scanner.Position + 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, endPos + default: + value.appendString(`\` + string(p.tok)) + } + endPos = p.scanner.Position + p.accept(p.tok) + case '#': + p.parseComment() + break loop + case '$': + var variable Variable + variable, endPos = p.parseVariable() + value.appendVariable(variable) + case scanner.EOF: + break loop + case '(': + if endParen { + parens++ + } + value.appendString("(") + endPos = p.scanner.Position + p.accept('(') + default: + value.appendString(p.scanner.TokenText()) + endPos = p.scanner.Position + p.accept(p.tok) + } + } + + if parens > 0 { + p.errorf("expected closing paren %s", value.Dump()) + } + return value, endPos +} + +func (p *parser) parseVariable() (Variable, scanner.Position) { + pos := p.scanner.Position + endPos := pos + p.accept('$') + var name *MakeString + switch p.tok { + case '(': + return p.parseBracketedVariable('(', ')', pos) + case '{': + return p.parseBracketedVariable('{', '}', pos) + case '$': + name = SimpleMakeString("__builtin_dollar", scanner.Position{}) + case scanner.EOF: + p.errorf("expected variable name, found %s", + scanner.TokenString(p.tok)) + default: + name, endPos = p.parseExpression(variableNameEndRunes...) + } + + return p.nameToVariable(name, pos, endPos), endPos +} + +func (p *parser) parseBracketedVariable(start, end rune, pos scanner.Position) (Variable, scanner.Position) { + p.accept(start) + name, endPos := p.parseExpression(end) + p.accept(end) + return p.nameToVariable(name, pos, endPos), endPos +} + +func (p *parser) nameToVariable(name *MakeString, pos, endPos scanner.Position) Variable { + return Variable{ + makeThing: makeThing{ + pos: pos, + endPos: endPos, + }, + Name: name, + } +} + +func (p *parser) parseRule(target *MakeString) { + prerequisites, newLine := p.parseRulePrerequisites(target) + + recipe := "" + endPos := p.scanner.Position +loop: + for { + if newLine { + if p.tok == '\t' { + endPos = p.scanner.Position + 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) + endPos = p.scanner.Position + p.accept(p.tok) + case '\n': + newLine = true + recipe += "\n" + endPos = p.scanner.Position + p.accept('\n') + case scanner.EOF: + break loop + default: + recipe += p.scanner.TokenText() + endPos = p.scanner.Position + p.accept(p.tok) + } + } + + if prerequisites != nil { + p.things = append(p.things, Rule{ + makeThing: makeThing{ + pos: target.Pos, + endPos: endPos, + }, + Target: target, + Prerequisites: prerequisites, + Recipe: recipe, + }) + } +} + +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 + default: + p.errorf("unexpected token %s after rule prerequisites", scanner.TokenString(p.tok)) + } + + return prerequisites, newLine +} + +func (p *parser) parseComment() { + pos := p.scanner.Position + p.accept('#') + comment := "" + endPos := pos +loop: + for { + switch p.tok { + case '\\': + p.parseEscape() + if p.tok == '\n' { + comment += "\n" + } else { + comment += "\\" + p.scanner.TokenText() + } + endPos = p.scanner.Position + p.accept(p.tok) + case '\n': + endPos = p.scanner.Position + p.accept('\n') + break loop + case scanner.EOF: + break loop + default: + comment += p.scanner.TokenText() + endPos = p.scanner.Position + p.accept(p.tok) + } + } + + p.comments = append(p.comments, Comment{ + makeThing: makeThing{ + pos: pos, + endPos: endPos, + }, + 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, endPos := p.parseExpression() + value.TrimLeftSpaces() + if ident.EndsWith('+') && t == "=" { + ident.TrimRightOne() + t = "+=" + } + + ident.TrimRightSpaces() + + p.things = append(p.things, Assignment{ + makeThing: makeThing{ + pos: ident.Pos, + endPos: endPos, + }, + 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) + } +} |