blob: 89ee308f78a0441c473961495f59c4223a20de54 [file] [log] [blame]
Colin Crossd00350c2017-11-17 10:55:38 -08001// Copyright 2017 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
Colin Cross3f40fa42015-01-30 17:27:36 -080015package parser
16
17import (
18 "errors"
19 "fmt"
20 "io"
21 "sort"
22 "text/scanner"
23)
24
25var errTooManyErrors = errors.New("too many errors")
26
27const maxErrors = 100
28
29type ParseError struct {
30 Err error
31 Pos scanner.Position
32}
33
34func (e *ParseError) Error() string {
35 return fmt.Sprintf("%s: %s", e.Pos, e.Err)
36}
37
Colin Cross08693d22016-05-25 17:25:40 -070038func (p *parser) Parse() ([]Node, []error) {
Colin Cross3f40fa42015-01-30 17:27:36 -080039 defer func() {
40 if r := recover(); r != nil {
41 if r == errTooManyErrors {
42 return
43 }
44 panic(r)
45 }
46 }()
47
48 p.parseLines()
49 p.accept(scanner.EOF)
Colin Cross08693d22016-05-25 17:25:40 -070050 p.nodes = append(p.nodes, p.comments...)
51 sort.Sort(byPosition(p.nodes))
Colin Cross3f40fa42015-01-30 17:27:36 -080052
Colin Cross08693d22016-05-25 17:25:40 -070053 return p.nodes, p.errors
Colin Cross3f40fa42015-01-30 17:27:36 -080054}
55
56type parser struct {
57 scanner scanner.Scanner
58 tok rune
59 errors []error
Colin Cross08693d22016-05-25 17:25:40 -070060 comments []Node
61 nodes []Node
62 lines []int
Colin Cross3f40fa42015-01-30 17:27:36 -080063}
64
65func NewParser(filename string, r io.Reader) *parser {
66 p := &parser{}
Colin Cross08693d22016-05-25 17:25:40 -070067 p.lines = []int{0}
Colin Cross3f40fa42015-01-30 17:27:36 -080068 p.scanner.Init(r)
69 p.scanner.Error = func(sc *scanner.Scanner, msg string) {
70 p.errorf(msg)
71 }
72 p.scanner.Whitespace = 0
73 p.scanner.IsIdentRune = func(ch rune, i int) bool {
74 return ch > 0 && ch != ':' && ch != '#' && ch != '=' && ch != '+' && ch != '$' &&
75 ch != '\\' && ch != '(' && ch != ')' && ch != '{' && ch != '}' && ch != ';' &&
76 ch != '|' && ch != '?' && ch != '\r' && !isWhitespace(ch)
77 }
78 p.scanner.Mode = scanner.ScanIdents
79 p.scanner.Filename = filename
80 p.next()
81 return p
82}
83
Colin Cross08693d22016-05-25 17:25:40 -070084func (p *parser) Unpack(pos Pos) scanner.Position {
85 offset := int(pos)
86 line := sort.Search(len(p.lines), func(i int) bool { return p.lines[i] > offset }) - 1
87 return scanner.Position{
88 Filename: p.scanner.Filename,
89 Line: line + 1,
90 Column: offset - p.lines[line] + 1,
91 Offset: offset,
92 }
93}
94
95func (p *parser) pos() Pos {
Colin Cross3f40fa42015-01-30 17:27:36 -080096 pos := p.scanner.Position
97 if !pos.IsValid() {
98 pos = p.scanner.Pos()
99 }
Colin Cross08693d22016-05-25 17:25:40 -0700100 return Pos(pos.Offset)
101}
102
103func (p *parser) errorf(format string, args ...interface{}) {
Colin Cross3f40fa42015-01-30 17:27:36 -0800104 err := &ParseError{
105 Err: fmt.Errorf(format, args...),
Colin Cross08693d22016-05-25 17:25:40 -0700106 Pos: p.scanner.Position,
Colin Cross3f40fa42015-01-30 17:27:36 -0800107 }
108 p.errors = append(p.errors, err)
109 if len(p.errors) >= maxErrors {
110 panic(errTooManyErrors)
111 }
112}
113
114func (p *parser) accept(toks ...rune) bool {
115 for _, tok := range toks {
116 if p.tok != tok {
117 p.errorf("expected %s, found %s", scanner.TokenString(tok),
118 scanner.TokenString(p.tok))
119 return false
120 }
121 p.next()
122 }
123 return true
124}
125
126func (p *parser) next() {
127 if p.tok != scanner.EOF {
128 p.tok = p.scanner.Scan()
129 for p.tok == '\r' {
130 p.tok = p.scanner.Scan()
131 }
132 }
Colin Cross08693d22016-05-25 17:25:40 -0700133 if p.tok == '\n' {
134 p.lines = append(p.lines, p.scanner.Position.Offset+1)
135 }
Colin Cross3f40fa42015-01-30 17:27:36 -0800136}
137
138func (p *parser) parseLines() {
139 for {
140 p.ignoreWhitespace()
141
142 if p.parseDirective() {
143 continue
144 }
145
Colin Cross08693d22016-05-25 17:25:40 -0700146 ident := p.parseExpression('=', '?', ':', '#', '\n')
Colin Cross3f40fa42015-01-30 17:27:36 -0800147
148 p.ignoreSpaces()
149
150 switch p.tok {
151 case '?':
152 p.accept('?')
153 if p.tok == '=' {
154 p.parseAssignment("?=", nil, ident)
155 } else {
156 p.errorf("expected = after ?")
157 }
158 case '+':
159 p.accept('+')
160 if p.tok == '=' {
161 p.parseAssignment("+=", nil, ident)
162 } else {
163 p.errorf("expected = after +")
164 }
165 case ':':
166 p.accept(':')
167 switch p.tok {
168 case '=':
169 p.parseAssignment(":=", nil, ident)
170 default:
171 p.parseRule(ident)
172 }
173 case '=':
174 p.parseAssignment("=", nil, ident)
175 case '#', '\n', scanner.EOF:
176 ident.TrimRightSpaces()
177 if v, ok := toVariable(ident); ok {
Colin Cross08693d22016-05-25 17:25:40 -0700178 p.nodes = append(p.nodes, &v)
Colin Cross3f40fa42015-01-30 17:27:36 -0800179 } else if !ident.Empty() {
180 p.errorf("expected directive, rule, or assignment after ident " + ident.Dump())
181 }
182 switch p.tok {
183 case scanner.EOF:
184 return
185 case '\n':
186 p.accept('\n')
187 case '#':
188 p.parseComment()
189 }
190 default:
191 p.errorf("expected assignment or rule definition, found %s\n",
192 p.scanner.TokenText())
193 return
194 }
195 }
196}
197
198func (p *parser) parseDirective() bool {
199 if p.tok != scanner.Ident || !isDirective(p.scanner.TokenText()) {
200 return false
201 }
202
203 d := p.scanner.TokenText()
Colin Cross08693d22016-05-25 17:25:40 -0700204 pos := p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800205 p.accept(scanner.Ident)
Colin Cross08693d22016-05-25 17:25:40 -0700206 endPos := NoPos
Colin Cross3f40fa42015-01-30 17:27:36 -0800207
208 expression := SimpleMakeString("", pos)
209
210 switch d {
211 case "endif", "endef", "else":
212 // Nothing
213 case "define":
Colin Cross08693d22016-05-25 17:25:40 -0700214 expression, endPos = p.parseDefine()
Colin Cross3f40fa42015-01-30 17:27:36 -0800215 default:
216 p.ignoreSpaces()
Colin Cross08693d22016-05-25 17:25:40 -0700217 expression = p.parseExpression()
Colin Cross3f40fa42015-01-30 17:27:36 -0800218 }
219
Colin Cross08693d22016-05-25 17:25:40 -0700220 p.nodes = append(p.nodes, &Directive{
221 NamePos: pos,
222 Name: d,
223 Args: expression,
224 EndPos: endPos,
Colin Cross3f40fa42015-01-30 17:27:36 -0800225 })
226 return true
227}
228
Colin Cross08693d22016-05-25 17:25:40 -0700229func (p *parser) parseDefine() (*MakeString, Pos) {
230 value := SimpleMakeString("", p.pos())
Colin Cross3f40fa42015-01-30 17:27:36 -0800231
232loop:
233 for {
234 switch p.tok {
235 case scanner.Ident:
Colin Cross08693d22016-05-25 17:25:40 -0700236 value.appendString(p.scanner.TokenText())
Colin Cross3f40fa42015-01-30 17:27:36 -0800237 if p.scanner.TokenText() == "endef" {
238 p.accept(scanner.Ident)
239 break loop
240 }
Colin Cross3f40fa42015-01-30 17:27:36 -0800241 p.accept(scanner.Ident)
242 case '\\':
243 p.parseEscape()
244 switch p.tok {
245 case '\n':
246 value.appendString(" ")
247 case scanner.EOF:
248 p.errorf("expected escaped character, found %s",
249 scanner.TokenString(p.tok))
250 break loop
251 default:
252 value.appendString(`\` + string(p.tok))
253 }
254 p.accept(p.tok)
255 //TODO: handle variables inside defines? result depends if
256 //define is used in make or rule context
257 //case '$':
258 // variable := p.parseVariable()
259 // value.appendVariable(variable)
260 case scanner.EOF:
261 p.errorf("unexpected EOF while looking for endef")
262 break loop
263 default:
264 value.appendString(p.scanner.TokenText())
265 p.accept(p.tok)
266 }
267 }
268
Colin Cross08693d22016-05-25 17:25:40 -0700269 return value, p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800270}
271
272func (p *parser) parseEscape() {
273 p.scanner.Mode = 0
274 p.accept('\\')
275 p.scanner.Mode = scanner.ScanIdents
276}
277
Colin Cross08693d22016-05-25 17:25:40 -0700278func (p *parser) parseExpression(end ...rune) *MakeString {
279 value := SimpleMakeString("", p.pos())
Colin Cross3f40fa42015-01-30 17:27:36 -0800280
281 endParen := false
282 for _, r := range end {
283 if r == ')' {
284 endParen = true
285 }
286 }
287 parens := 0
288
Colin Cross3f40fa42015-01-30 17:27:36 -0800289loop:
290 for {
291 if endParen && parens > 0 && p.tok == ')' {
292 parens--
293 value.appendString(")")
Colin Cross3f40fa42015-01-30 17:27:36 -0800294 p.accept(')')
295 continue
296 }
297
298 for _, r := range end {
299 if p.tok == r {
300 break loop
301 }
302 }
303
304 switch p.tok {
305 case '\n':
306 break loop
307 case scanner.Ident:
308 value.appendString(p.scanner.TokenText())
Colin Cross3f40fa42015-01-30 17:27:36 -0800309 p.accept(scanner.Ident)
310 case '\\':
311 p.parseEscape()
312 switch p.tok {
313 case '\n':
314 value.appendString(" ")
315 case scanner.EOF:
316 p.errorf("expected escaped character, found %s",
317 scanner.TokenString(p.tok))
Colin Cross08693d22016-05-25 17:25:40 -0700318 return value
Colin Cross3f40fa42015-01-30 17:27:36 -0800319 default:
320 value.appendString(`\` + string(p.tok))
321 }
Colin Cross3f40fa42015-01-30 17:27:36 -0800322 p.accept(p.tok)
323 case '#':
324 p.parseComment()
325 break loop
326 case '$':
327 var variable Variable
Colin Cross08693d22016-05-25 17:25:40 -0700328 variable = p.parseVariable()
Colin Cross3f40fa42015-01-30 17:27:36 -0800329 value.appendVariable(variable)
330 case scanner.EOF:
331 break loop
332 case '(':
333 if endParen {
334 parens++
335 }
336 value.appendString("(")
Colin Cross3f40fa42015-01-30 17:27:36 -0800337 p.accept('(')
338 default:
339 value.appendString(p.scanner.TokenText())
Colin Cross3f40fa42015-01-30 17:27:36 -0800340 p.accept(p.tok)
341 }
342 }
343
344 if parens > 0 {
345 p.errorf("expected closing paren %s", value.Dump())
346 }
Colin Cross08693d22016-05-25 17:25:40 -0700347 return value
Colin Cross3f40fa42015-01-30 17:27:36 -0800348}
349
Colin Cross08693d22016-05-25 17:25:40 -0700350func (p *parser) parseVariable() Variable {
351 pos := p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800352 p.accept('$')
353 var name *MakeString
354 switch p.tok {
355 case '(':
356 return p.parseBracketedVariable('(', ')', pos)
357 case '{':
358 return p.parseBracketedVariable('{', '}', pos)
359 case '$':
Colin Cross08693d22016-05-25 17:25:40 -0700360 name = SimpleMakeString("__builtin_dollar", NoPos)
Colin Cross3f40fa42015-01-30 17:27:36 -0800361 case scanner.EOF:
362 p.errorf("expected variable name, found %s",
363 scanner.TokenString(p.tok))
364 default:
Colin Cross08693d22016-05-25 17:25:40 -0700365 name = p.parseExpression(variableNameEndRunes...)
Colin Cross3f40fa42015-01-30 17:27:36 -0800366 }
367
Colin Cross08693d22016-05-25 17:25:40 -0700368 return p.nameToVariable(name)
Colin Cross3f40fa42015-01-30 17:27:36 -0800369}
370
Colin Cross08693d22016-05-25 17:25:40 -0700371func (p *parser) parseBracketedVariable(start, end rune, pos Pos) Variable {
Colin Cross3f40fa42015-01-30 17:27:36 -0800372 p.accept(start)
Colin Cross08693d22016-05-25 17:25:40 -0700373 name := p.parseExpression(end)
Colin Cross3f40fa42015-01-30 17:27:36 -0800374 p.accept(end)
Colin Cross08693d22016-05-25 17:25:40 -0700375 return p.nameToVariable(name)
Colin Cross3f40fa42015-01-30 17:27:36 -0800376}
377
Colin Cross08693d22016-05-25 17:25:40 -0700378func (p *parser) nameToVariable(name *MakeString) Variable {
Colin Cross3f40fa42015-01-30 17:27:36 -0800379 return Variable{
Colin Cross3f40fa42015-01-30 17:27:36 -0800380 Name: name,
381 }
382}
383
384func (p *parser) parseRule(target *MakeString) {
385 prerequisites, newLine := p.parseRulePrerequisites(target)
386
387 recipe := ""
Colin Cross08693d22016-05-25 17:25:40 -0700388 recipePos := p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800389loop:
390 for {
391 if newLine {
392 if p.tok == '\t' {
Colin Cross3f40fa42015-01-30 17:27:36 -0800393 p.accept('\t')
394 newLine = false
395 continue loop
396 } else if p.parseDirective() {
397 newLine = false
398 continue
399 } else {
400 break loop
401 }
402 }
403
404 newLine = false
405 switch p.tok {
406 case '\\':
407 p.parseEscape()
408 recipe += string(p.tok)
Colin Cross3f40fa42015-01-30 17:27:36 -0800409 p.accept(p.tok)
410 case '\n':
411 newLine = true
412 recipe += "\n"
Colin Cross3f40fa42015-01-30 17:27:36 -0800413 p.accept('\n')
414 case scanner.EOF:
415 break loop
416 default:
417 recipe += p.scanner.TokenText()
Colin Cross3f40fa42015-01-30 17:27:36 -0800418 p.accept(p.tok)
419 }
420 }
421
422 if prerequisites != nil {
Colin Cross08693d22016-05-25 17:25:40 -0700423 p.nodes = append(p.nodes, &Rule{
Colin Cross3f40fa42015-01-30 17:27:36 -0800424 Target: target,
425 Prerequisites: prerequisites,
426 Recipe: recipe,
Colin Cross08693d22016-05-25 17:25:40 -0700427 RecipePos: recipePos,
Colin Cross3f40fa42015-01-30 17:27:36 -0800428 })
429 }
430}
431
432func (p *parser) parseRulePrerequisites(target *MakeString) (*MakeString, bool) {
433 newLine := false
434
435 p.ignoreSpaces()
436
Colin Cross08693d22016-05-25 17:25:40 -0700437 prerequisites := p.parseExpression('#', '\n', ';', ':', '=')
Colin Cross3f40fa42015-01-30 17:27:36 -0800438
439 switch p.tok {
440 case '\n':
441 p.accept('\n')
442 newLine = true
443 case '#':
444 p.parseComment()
445 newLine = true
446 case ';':
447 p.accept(';')
448 case ':':
449 p.accept(':')
450 if p.tok == '=' {
451 p.parseAssignment(":=", target, prerequisites)
452 return nil, true
453 } else {
Colin Cross08693d22016-05-25 17:25:40 -0700454 more := p.parseExpression('#', '\n', ';')
Colin Cross3f40fa42015-01-30 17:27:36 -0800455 prerequisites.appendMakeString(more)
456 }
457 case '=':
458 p.parseAssignment("=", target, prerequisites)
459 return nil, true
460 default:
461 p.errorf("unexpected token %s after rule prerequisites", scanner.TokenString(p.tok))
462 }
463
464 return prerequisites, newLine
465}
466
467func (p *parser) parseComment() {
Colin Cross08693d22016-05-25 17:25:40 -0700468 pos := p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800469 p.accept('#')
470 comment := ""
Colin Cross3f40fa42015-01-30 17:27:36 -0800471loop:
472 for {
473 switch p.tok {
474 case '\\':
475 p.parseEscape()
476 if p.tok == '\n' {
477 comment += "\n"
478 } else {
479 comment += "\\" + p.scanner.TokenText()
480 }
Colin Cross3f40fa42015-01-30 17:27:36 -0800481 p.accept(p.tok)
482 case '\n':
Colin Cross3f40fa42015-01-30 17:27:36 -0800483 p.accept('\n')
484 break loop
485 case scanner.EOF:
486 break loop
487 default:
488 comment += p.scanner.TokenText()
Colin Cross3f40fa42015-01-30 17:27:36 -0800489 p.accept(p.tok)
490 }
491 }
492
Colin Cross08693d22016-05-25 17:25:40 -0700493 p.comments = append(p.comments, &Comment{
494 CommentPos: pos,
495 Comment: comment,
Colin Cross3f40fa42015-01-30 17:27:36 -0800496 })
497}
498
499func (p *parser) parseAssignment(t string, target *MakeString, ident *MakeString) {
500 // The value of an assignment is everything including and after the first
501 // non-whitespace character after the = until the end of the logical line,
502 // which may included escaped newlines
503 p.accept('=')
Colin Cross08693d22016-05-25 17:25:40 -0700504 value := p.parseExpression()
Colin Cross3f40fa42015-01-30 17:27:36 -0800505 value.TrimLeftSpaces()
506 if ident.EndsWith('+') && t == "=" {
507 ident.TrimRightOne()
508 t = "+="
509 }
510
511 ident.TrimRightSpaces()
512
Colin Cross08693d22016-05-25 17:25:40 -0700513 p.nodes = append(p.nodes, &Assignment{
Colin Cross3f40fa42015-01-30 17:27:36 -0800514 Name: ident,
515 Value: value,
516 Target: target,
517 Type: t,
518 })
519}
520
521type androidMkModule struct {
522 assignments map[string]string
523}
524
525type androidMkFile struct {
526 assignments map[string]string
527 modules []androidMkModule
528 includes []string
529}
530
531var directives = [...]string{
532 "define",
533 "else",
534 "endef",
535 "endif",
536 "ifdef",
537 "ifeq",
538 "ifndef",
539 "ifneq",
540 "include",
541 "-include",
542}
543
544var functions = [...]string{
545 "abspath",
546 "addprefix",
547 "addsuffix",
548 "basename",
549 "dir",
550 "notdir",
551 "subst",
552 "suffix",
553 "filter",
554 "filter-out",
555 "findstring",
556 "firstword",
557 "flavor",
558 "join",
559 "lastword",
560 "patsubst",
561 "realpath",
562 "shell",
563 "sort",
564 "strip",
565 "wildcard",
566 "word",
567 "wordlist",
568 "words",
569 "origin",
570 "foreach",
571 "call",
572 "info",
573 "error",
574 "warning",
575 "if",
576 "or",
577 "and",
578 "value",
579 "eval",
580 "file",
581}
582
583func init() {
584 sort.Strings(directives[:])
585 sort.Strings(functions[:])
586}
587
588func isDirective(s string) bool {
589 for _, d := range directives {
590 if s == d {
591 return true
592 } else if s < d {
593 return false
594 }
595 }
596 return false
597}
598
599func isFunctionName(s string) bool {
600 for _, f := range functions {
601 if s == f {
602 return true
603 } else if s < f {
604 return false
605 }
606 }
607 return false
608}
609
610func isWhitespace(ch rune) bool {
611 return ch == ' ' || ch == '\t' || ch == '\n'
612}
613
614func isValidVariableRune(ch rune) bool {
615 return ch != scanner.Ident && ch != ':' && ch != '=' && ch != '#'
616}
617
618var whitespaceRunes = []rune{' ', '\t', '\n'}
619var variableNameEndRunes = append([]rune{':', '=', '#', ')', '}'}, whitespaceRunes...)
620
621func (p *parser) ignoreSpaces() int {
622 skipped := 0
623 for p.tok == ' ' || p.tok == '\t' {
624 p.accept(p.tok)
625 skipped++
626 }
627 return skipped
628}
629
630func (p *parser) ignoreWhitespace() {
631 for isWhitespace(p.tok) {
632 p.accept(p.tok)
633 }
634}