blob: fb6be38e94db2bfbc234176841323f1e1d434615 [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
Dan Willemsen43398532018-02-21 02:10:29 -080038const builtinDollar = "__builtin_dollar"
39
40var builtinDollarName = SimpleMakeString(builtinDollar, NoPos)
41
Colin Cross08693d22016-05-25 17:25:40 -070042func (p *parser) Parse() ([]Node, []error) {
Colin Cross3f40fa42015-01-30 17:27:36 -080043 defer func() {
44 if r := recover(); r != nil {
45 if r == errTooManyErrors {
46 return
47 }
48 panic(r)
49 }
50 }()
51
52 p.parseLines()
53 p.accept(scanner.EOF)
Colin Cross08693d22016-05-25 17:25:40 -070054 p.nodes = append(p.nodes, p.comments...)
55 sort.Sort(byPosition(p.nodes))
Colin Cross3f40fa42015-01-30 17:27:36 -080056
Colin Cross08693d22016-05-25 17:25:40 -070057 return p.nodes, p.errors
Colin Cross3f40fa42015-01-30 17:27:36 -080058}
59
60type parser struct {
61 scanner scanner.Scanner
62 tok rune
63 errors []error
Colin Cross08693d22016-05-25 17:25:40 -070064 comments []Node
65 nodes []Node
66 lines []int
Colin Cross3f40fa42015-01-30 17:27:36 -080067}
68
69func NewParser(filename string, r io.Reader) *parser {
70 p := &parser{}
Colin Cross08693d22016-05-25 17:25:40 -070071 p.lines = []int{0}
Colin Cross3f40fa42015-01-30 17:27:36 -080072 p.scanner.Init(r)
73 p.scanner.Error = func(sc *scanner.Scanner, msg string) {
74 p.errorf(msg)
75 }
76 p.scanner.Whitespace = 0
77 p.scanner.IsIdentRune = func(ch rune, i int) bool {
78 return ch > 0 && ch != ':' && ch != '#' && ch != '=' && ch != '+' && ch != '$' &&
79 ch != '\\' && ch != '(' && ch != ')' && ch != '{' && ch != '}' && ch != ';' &&
80 ch != '|' && ch != '?' && ch != '\r' && !isWhitespace(ch)
81 }
82 p.scanner.Mode = scanner.ScanIdents
83 p.scanner.Filename = filename
84 p.next()
85 return p
86}
87
Colin Cross08693d22016-05-25 17:25:40 -070088func (p *parser) Unpack(pos Pos) scanner.Position {
89 offset := int(pos)
90 line := sort.Search(len(p.lines), func(i int) bool { return p.lines[i] > offset }) - 1
91 return scanner.Position{
92 Filename: p.scanner.Filename,
93 Line: line + 1,
94 Column: offset - p.lines[line] + 1,
95 Offset: offset,
96 }
97}
98
99func (p *parser) pos() Pos {
Colin Cross3f40fa42015-01-30 17:27:36 -0800100 pos := p.scanner.Position
101 if !pos.IsValid() {
102 pos = p.scanner.Pos()
103 }
Colin Cross08693d22016-05-25 17:25:40 -0700104 return Pos(pos.Offset)
105}
106
107func (p *parser) errorf(format string, args ...interface{}) {
Colin Cross3f40fa42015-01-30 17:27:36 -0800108 err := &ParseError{
109 Err: fmt.Errorf(format, args...),
Colin Cross08693d22016-05-25 17:25:40 -0700110 Pos: p.scanner.Position,
Colin Cross3f40fa42015-01-30 17:27:36 -0800111 }
112 p.errors = append(p.errors, err)
113 if len(p.errors) >= maxErrors {
114 panic(errTooManyErrors)
115 }
116}
117
118func (p *parser) accept(toks ...rune) bool {
119 for _, tok := range toks {
120 if p.tok != tok {
121 p.errorf("expected %s, found %s", scanner.TokenString(tok),
122 scanner.TokenString(p.tok))
123 return false
124 }
125 p.next()
126 }
127 return true
128}
129
130func (p *parser) next() {
131 if p.tok != scanner.EOF {
132 p.tok = p.scanner.Scan()
133 for p.tok == '\r' {
134 p.tok = p.scanner.Scan()
135 }
136 }
Colin Cross08693d22016-05-25 17:25:40 -0700137 if p.tok == '\n' {
138 p.lines = append(p.lines, p.scanner.Position.Offset+1)
139 }
Colin Cross3f40fa42015-01-30 17:27:36 -0800140}
141
142func (p *parser) parseLines() {
143 for {
144 p.ignoreWhitespace()
145
146 if p.parseDirective() {
147 continue
148 }
149
Colin Cross08693d22016-05-25 17:25:40 -0700150 ident := p.parseExpression('=', '?', ':', '#', '\n')
Colin Cross3f40fa42015-01-30 17:27:36 -0800151
152 p.ignoreSpaces()
153
154 switch p.tok {
155 case '?':
156 p.accept('?')
157 if p.tok == '=' {
158 p.parseAssignment("?=", nil, ident)
159 } else {
160 p.errorf("expected = after ?")
161 }
162 case '+':
163 p.accept('+')
164 if p.tok == '=' {
165 p.parseAssignment("+=", nil, ident)
166 } else {
167 p.errorf("expected = after +")
168 }
169 case ':':
170 p.accept(':')
171 switch p.tok {
172 case '=':
173 p.parseAssignment(":=", nil, ident)
174 default:
175 p.parseRule(ident)
176 }
177 case '=':
178 p.parseAssignment("=", nil, ident)
179 case '#', '\n', scanner.EOF:
180 ident.TrimRightSpaces()
181 if v, ok := toVariable(ident); ok {
Colin Cross08693d22016-05-25 17:25:40 -0700182 p.nodes = append(p.nodes, &v)
Colin Cross3f40fa42015-01-30 17:27:36 -0800183 } else if !ident.Empty() {
184 p.errorf("expected directive, rule, or assignment after ident " + ident.Dump())
185 }
186 switch p.tok {
187 case scanner.EOF:
188 return
189 case '\n':
190 p.accept('\n')
191 case '#':
192 p.parseComment()
193 }
194 default:
195 p.errorf("expected assignment or rule definition, found %s\n",
196 p.scanner.TokenText())
197 return
198 }
199 }
200}
201
202func (p *parser) parseDirective() bool {
203 if p.tok != scanner.Ident || !isDirective(p.scanner.TokenText()) {
204 return false
205 }
206
207 d := p.scanner.TokenText()
Colin Cross08693d22016-05-25 17:25:40 -0700208 pos := p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800209 p.accept(scanner.Ident)
Colin Cross08693d22016-05-25 17:25:40 -0700210 endPos := NoPos
Colin Cross3f40fa42015-01-30 17:27:36 -0800211
212 expression := SimpleMakeString("", pos)
213
214 switch d {
Sasha Smundak9c35d8b2020-11-17 22:58:55 -0800215 case "endif", "endef":
Colin Cross3f40fa42015-01-30 17:27:36 -0800216 // Nothing
Sasha Smundak9c35d8b2020-11-17 22:58:55 -0800217 case "else":
218 p.ignoreSpaces()
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800219 if p.tok != '\n' && p.tok != '#' {
Sasha Smundak9c35d8b2020-11-17 22:58:55 -0800220 d = p.scanner.TokenText()
221 p.accept(scanner.Ident)
222 if d == "ifdef" || d == "ifndef" || d == "ifeq" || d == "ifneq" {
223 d = "el" + d
224 p.ignoreSpaces()
Cole Fauste309a912022-03-16 13:42:34 -0700225 expression = p.parseExpression('#')
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800226 expression.TrimRightSpaces()
Sasha Smundak9c35d8b2020-11-17 22:58:55 -0800227 } else {
228 p.errorf("expected ifdef/ifndef/ifeq/ifneq, found %s", d)
229 }
230 }
Colin Cross3f40fa42015-01-30 17:27:36 -0800231 case "define":
Colin Cross08693d22016-05-25 17:25:40 -0700232 expression, endPos = p.parseDefine()
Colin Cross3f40fa42015-01-30 17:27:36 -0800233 default:
234 p.ignoreSpaces()
Cole Fauste309a912022-03-16 13:42:34 -0700235 expression = p.parseExpression('#')
Colin Cross3f40fa42015-01-30 17:27:36 -0800236 }
237
Colin Cross08693d22016-05-25 17:25:40 -0700238 p.nodes = append(p.nodes, &Directive{
239 NamePos: pos,
240 Name: d,
241 Args: expression,
242 EndPos: endPos,
Colin Cross3f40fa42015-01-30 17:27:36 -0800243 })
244 return true
245}
246
Colin Cross08693d22016-05-25 17:25:40 -0700247func (p *parser) parseDefine() (*MakeString, Pos) {
248 value := SimpleMakeString("", p.pos())
Colin Cross3f40fa42015-01-30 17:27:36 -0800249
250loop:
251 for {
252 switch p.tok {
253 case scanner.Ident:
Colin Cross08693d22016-05-25 17:25:40 -0700254 value.appendString(p.scanner.TokenText())
Colin Cross3f40fa42015-01-30 17:27:36 -0800255 if p.scanner.TokenText() == "endef" {
256 p.accept(scanner.Ident)
257 break loop
258 }
Colin Cross3f40fa42015-01-30 17:27:36 -0800259 p.accept(scanner.Ident)
260 case '\\':
261 p.parseEscape()
262 switch p.tok {
263 case '\n':
264 value.appendString(" ")
265 case scanner.EOF:
266 p.errorf("expected escaped character, found %s",
267 scanner.TokenString(p.tok))
268 break loop
269 default:
270 value.appendString(`\` + string(p.tok))
271 }
272 p.accept(p.tok)
273 //TODO: handle variables inside defines? result depends if
274 //define is used in make or rule context
275 //case '$':
276 // variable := p.parseVariable()
277 // value.appendVariable(variable)
278 case scanner.EOF:
279 p.errorf("unexpected EOF while looking for endef")
280 break loop
281 default:
282 value.appendString(p.scanner.TokenText())
283 p.accept(p.tok)
284 }
285 }
286
Colin Cross08693d22016-05-25 17:25:40 -0700287 return value, p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800288}
289
290func (p *parser) parseEscape() {
291 p.scanner.Mode = 0
292 p.accept('\\')
293 p.scanner.Mode = scanner.ScanIdents
294}
295
Colin Cross08693d22016-05-25 17:25:40 -0700296func (p *parser) parseExpression(end ...rune) *MakeString {
297 value := SimpleMakeString("", p.pos())
Colin Cross3f40fa42015-01-30 17:27:36 -0800298
299 endParen := false
300 for _, r := range end {
301 if r == ')' {
302 endParen = true
303 }
304 }
305 parens := 0
306
Colin Cross3f40fa42015-01-30 17:27:36 -0800307loop:
308 for {
309 if endParen && parens > 0 && p.tok == ')' {
310 parens--
311 value.appendString(")")
Colin Cross3f40fa42015-01-30 17:27:36 -0800312 p.accept(')')
313 continue
314 }
315
316 for _, r := range end {
317 if p.tok == r {
318 break loop
319 }
320 }
321
322 switch p.tok {
323 case '\n':
324 break loop
325 case scanner.Ident:
326 value.appendString(p.scanner.TokenText())
Colin Cross3f40fa42015-01-30 17:27:36 -0800327 p.accept(scanner.Ident)
328 case '\\':
329 p.parseEscape()
330 switch p.tok {
331 case '\n':
332 value.appendString(" ")
333 case scanner.EOF:
334 p.errorf("expected escaped character, found %s",
335 scanner.TokenString(p.tok))
Colin Cross08693d22016-05-25 17:25:40 -0700336 return value
Colin Cross3f40fa42015-01-30 17:27:36 -0800337 default:
338 value.appendString(`\` + string(p.tok))
339 }
Colin Cross3f40fa42015-01-30 17:27:36 -0800340 p.accept(p.tok)
Colin Cross3f40fa42015-01-30 17:27:36 -0800341 case '$':
342 var variable Variable
Colin Cross08693d22016-05-25 17:25:40 -0700343 variable = p.parseVariable()
Dan Willemsen43398532018-02-21 02:10:29 -0800344 if variable.Name == builtinDollarName {
345 value.appendString("$")
346 } else {
347 value.appendVariable(variable)
348 }
Colin Cross3f40fa42015-01-30 17:27:36 -0800349 case scanner.EOF:
350 break loop
351 case '(':
352 if endParen {
353 parens++
354 }
355 value.appendString("(")
Colin Cross3f40fa42015-01-30 17:27:36 -0800356 p.accept('(')
357 default:
358 value.appendString(p.scanner.TokenText())
Colin Cross3f40fa42015-01-30 17:27:36 -0800359 p.accept(p.tok)
360 }
361 }
362
363 if parens > 0 {
364 p.errorf("expected closing paren %s", value.Dump())
365 }
Colin Cross08693d22016-05-25 17:25:40 -0700366 return value
Colin Cross3f40fa42015-01-30 17:27:36 -0800367}
368
Colin Cross08693d22016-05-25 17:25:40 -0700369func (p *parser) parseVariable() Variable {
370 pos := p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800371 p.accept('$')
372 var name *MakeString
373 switch p.tok {
374 case '(':
375 return p.parseBracketedVariable('(', ')', pos)
376 case '{':
377 return p.parseBracketedVariable('{', '}', pos)
378 case '$':
Dan Willemsen43398532018-02-21 02:10:29 -0800379 name = builtinDollarName
380 p.accept(p.tok)
Colin Cross3f40fa42015-01-30 17:27:36 -0800381 case scanner.EOF:
382 p.errorf("expected variable name, found %s",
383 scanner.TokenString(p.tok))
384 default:
Colin Cross08693d22016-05-25 17:25:40 -0700385 name = p.parseExpression(variableNameEndRunes...)
Colin Cross3f40fa42015-01-30 17:27:36 -0800386 }
387
Colin Cross08693d22016-05-25 17:25:40 -0700388 return p.nameToVariable(name)
Colin Cross3f40fa42015-01-30 17:27:36 -0800389}
390
Colin Cross08693d22016-05-25 17:25:40 -0700391func (p *parser) parseBracketedVariable(start, end rune, pos Pos) Variable {
Colin Cross3f40fa42015-01-30 17:27:36 -0800392 p.accept(start)
Colin Cross08693d22016-05-25 17:25:40 -0700393 name := p.parseExpression(end)
Colin Cross3f40fa42015-01-30 17:27:36 -0800394 p.accept(end)
Colin Cross08693d22016-05-25 17:25:40 -0700395 return p.nameToVariable(name)
Colin Cross3f40fa42015-01-30 17:27:36 -0800396}
397
Colin Cross08693d22016-05-25 17:25:40 -0700398func (p *parser) nameToVariable(name *MakeString) Variable {
Colin Cross3f40fa42015-01-30 17:27:36 -0800399 return Variable{
Colin Cross3f40fa42015-01-30 17:27:36 -0800400 Name: name,
401 }
402}
403
404func (p *parser) parseRule(target *MakeString) {
405 prerequisites, newLine := p.parseRulePrerequisites(target)
406
407 recipe := ""
Colin Cross08693d22016-05-25 17:25:40 -0700408 recipePos := p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800409loop:
410 for {
411 if newLine {
412 if p.tok == '\t' {
Colin Cross3f40fa42015-01-30 17:27:36 -0800413 p.accept('\t')
414 newLine = false
415 continue loop
416 } else if p.parseDirective() {
417 newLine = false
418 continue
419 } else {
420 break loop
421 }
422 }
423
424 newLine = false
425 switch p.tok {
426 case '\\':
427 p.parseEscape()
428 recipe += string(p.tok)
Colin Cross3f40fa42015-01-30 17:27:36 -0800429 p.accept(p.tok)
430 case '\n':
431 newLine = true
432 recipe += "\n"
Colin Cross3f40fa42015-01-30 17:27:36 -0800433 p.accept('\n')
434 case scanner.EOF:
435 break loop
436 default:
437 recipe += p.scanner.TokenText()
Colin Cross3f40fa42015-01-30 17:27:36 -0800438 p.accept(p.tok)
439 }
440 }
441
442 if prerequisites != nil {
Colin Cross08693d22016-05-25 17:25:40 -0700443 p.nodes = append(p.nodes, &Rule{
Colin Cross3f40fa42015-01-30 17:27:36 -0800444 Target: target,
445 Prerequisites: prerequisites,
446 Recipe: recipe,
Colin Cross08693d22016-05-25 17:25:40 -0700447 RecipePos: recipePos,
Colin Cross3f40fa42015-01-30 17:27:36 -0800448 })
449 }
450}
451
452func (p *parser) parseRulePrerequisites(target *MakeString) (*MakeString, bool) {
453 newLine := false
454
455 p.ignoreSpaces()
456
Colin Cross08693d22016-05-25 17:25:40 -0700457 prerequisites := p.parseExpression('#', '\n', ';', ':', '=')
Colin Cross3f40fa42015-01-30 17:27:36 -0800458
459 switch p.tok {
460 case '\n':
461 p.accept('\n')
462 newLine = true
463 case '#':
464 p.parseComment()
465 newLine = true
466 case ';':
467 p.accept(';')
468 case ':':
469 p.accept(':')
470 if p.tok == '=' {
471 p.parseAssignment(":=", target, prerequisites)
472 return nil, true
473 } else {
Colin Cross08693d22016-05-25 17:25:40 -0700474 more := p.parseExpression('#', '\n', ';')
Colin Cross3f40fa42015-01-30 17:27:36 -0800475 prerequisites.appendMakeString(more)
476 }
477 case '=':
478 p.parseAssignment("=", target, prerequisites)
479 return nil, true
Dan Willemsen43398532018-02-21 02:10:29 -0800480 case scanner.EOF:
481 // do nothing
Colin Cross3f40fa42015-01-30 17:27:36 -0800482 default:
483 p.errorf("unexpected token %s after rule prerequisites", scanner.TokenString(p.tok))
484 }
485
486 return prerequisites, newLine
487}
488
489func (p *parser) parseComment() {
Colin Cross08693d22016-05-25 17:25:40 -0700490 pos := p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800491 p.accept('#')
492 comment := ""
Colin Cross3f40fa42015-01-30 17:27:36 -0800493loop:
494 for {
495 switch p.tok {
496 case '\\':
497 p.parseEscape()
Sasha Smundake10952b2019-03-05 17:34:32 -0800498 comment += "\\" + p.scanner.TokenText()
Colin Cross3f40fa42015-01-30 17:27:36 -0800499 p.accept(p.tok)
500 case '\n':
Colin Cross3f40fa42015-01-30 17:27:36 -0800501 p.accept('\n')
502 break loop
503 case scanner.EOF:
504 break loop
505 default:
506 comment += p.scanner.TokenText()
Colin Cross3f40fa42015-01-30 17:27:36 -0800507 p.accept(p.tok)
508 }
509 }
510
Colin Cross08693d22016-05-25 17:25:40 -0700511 p.comments = append(p.comments, &Comment{
512 CommentPos: pos,
513 Comment: comment,
Colin Cross3f40fa42015-01-30 17:27:36 -0800514 })
515}
516
517func (p *parser) parseAssignment(t string, target *MakeString, ident *MakeString) {
518 // The value of an assignment is everything including and after the first
519 // non-whitespace character after the = until the end of the logical line,
520 // which may included escaped newlines
521 p.accept('=')
Cole Fauste309a912022-03-16 13:42:34 -0700522 value := p.parseExpression('#')
Colin Cross3f40fa42015-01-30 17:27:36 -0800523 value.TrimLeftSpaces()
524 if ident.EndsWith('+') && t == "=" {
525 ident.TrimRightOne()
526 t = "+="
527 }
528
529 ident.TrimRightSpaces()
530
Colin Cross08693d22016-05-25 17:25:40 -0700531 p.nodes = append(p.nodes, &Assignment{
Colin Cross3f40fa42015-01-30 17:27:36 -0800532 Name: ident,
533 Value: value,
534 Target: target,
535 Type: t,
536 })
537}
538
539type androidMkModule struct {
540 assignments map[string]string
541}
542
543type androidMkFile struct {
544 assignments map[string]string
545 modules []androidMkModule
546 includes []string
547}
548
549var directives = [...]string{
550 "define",
551 "else",
552 "endef",
553 "endif",
Sasha Smundakd63f7f02020-11-29 12:52:47 -0800554 "export",
Colin Cross3f40fa42015-01-30 17:27:36 -0800555 "ifdef",
556 "ifeq",
557 "ifndef",
558 "ifneq",
559 "include",
560 "-include",
Sasha Smundakd63f7f02020-11-29 12:52:47 -0800561 "unexport",
Colin Cross3f40fa42015-01-30 17:27:36 -0800562}
563
564var functions = [...]string{
565 "abspath",
566 "addprefix",
567 "addsuffix",
568 "basename",
569 "dir",
570 "notdir",
571 "subst",
572 "suffix",
573 "filter",
574 "filter-out",
575 "findstring",
576 "firstword",
577 "flavor",
578 "join",
579 "lastword",
580 "patsubst",
581 "realpath",
582 "shell",
583 "sort",
584 "strip",
585 "wildcard",
586 "word",
587 "wordlist",
588 "words",
589 "origin",
590 "foreach",
591 "call",
592 "info",
593 "error",
594 "warning",
595 "if",
596 "or",
597 "and",
598 "value",
599 "eval",
600 "file",
601}
602
603func init() {
604 sort.Strings(directives[:])
605 sort.Strings(functions[:])
606}
607
608func isDirective(s string) bool {
609 for _, d := range directives {
610 if s == d {
611 return true
612 } else if s < d {
613 return false
614 }
615 }
616 return false
617}
618
619func isFunctionName(s string) bool {
620 for _, f := range functions {
621 if s == f {
622 return true
623 } else if s < f {
624 return false
625 }
626 }
627 return false
628}
629
630func isWhitespace(ch rune) bool {
631 return ch == ' ' || ch == '\t' || ch == '\n'
632}
633
634func isValidVariableRune(ch rune) bool {
635 return ch != scanner.Ident && ch != ':' && ch != '=' && ch != '#'
636}
637
638var whitespaceRunes = []rune{' ', '\t', '\n'}
639var variableNameEndRunes = append([]rune{':', '=', '#', ')', '}'}, whitespaceRunes...)
640
641func (p *parser) ignoreSpaces() int {
642 skipped := 0
643 for p.tok == ' ' || p.tok == '\t' {
644 p.accept(p.tok)
645 skipped++
646 }
647 return skipped
648}
649
650func (p *parser) ignoreWhitespace() {
651 for isWhitespace(p.tok) {
652 p.accept(p.tok)
653 }
654}