blob: e61241b2d3f0f74c0dda225f1a9fa37358d83324 [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()
219 if p.tok != '\n' {
220 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()
225 expression = p.parseExpression()
226 } else {
227 p.errorf("expected ifdef/ifndef/ifeq/ifneq, found %s", d)
228 }
229 }
Colin Cross3f40fa42015-01-30 17:27:36 -0800230 case "define":
Colin Cross08693d22016-05-25 17:25:40 -0700231 expression, endPos = p.parseDefine()
Colin Cross3f40fa42015-01-30 17:27:36 -0800232 default:
233 p.ignoreSpaces()
Colin Cross08693d22016-05-25 17:25:40 -0700234 expression = p.parseExpression()
Colin Cross3f40fa42015-01-30 17:27:36 -0800235 }
236
Colin Cross08693d22016-05-25 17:25:40 -0700237 p.nodes = append(p.nodes, &Directive{
238 NamePos: pos,
239 Name: d,
240 Args: expression,
241 EndPos: endPos,
Colin Cross3f40fa42015-01-30 17:27:36 -0800242 })
243 return true
244}
245
Colin Cross08693d22016-05-25 17:25:40 -0700246func (p *parser) parseDefine() (*MakeString, Pos) {
247 value := SimpleMakeString("", p.pos())
Colin Cross3f40fa42015-01-30 17:27:36 -0800248
249loop:
250 for {
251 switch p.tok {
252 case scanner.Ident:
Colin Cross08693d22016-05-25 17:25:40 -0700253 value.appendString(p.scanner.TokenText())
Colin Cross3f40fa42015-01-30 17:27:36 -0800254 if p.scanner.TokenText() == "endef" {
255 p.accept(scanner.Ident)
256 break loop
257 }
Colin Cross3f40fa42015-01-30 17:27:36 -0800258 p.accept(scanner.Ident)
259 case '\\':
260 p.parseEscape()
261 switch p.tok {
262 case '\n':
263 value.appendString(" ")
264 case scanner.EOF:
265 p.errorf("expected escaped character, found %s",
266 scanner.TokenString(p.tok))
267 break loop
268 default:
269 value.appendString(`\` + string(p.tok))
270 }
271 p.accept(p.tok)
272 //TODO: handle variables inside defines? result depends if
273 //define is used in make or rule context
274 //case '$':
275 // variable := p.parseVariable()
276 // value.appendVariable(variable)
277 case scanner.EOF:
278 p.errorf("unexpected EOF while looking for endef")
279 break loop
280 default:
281 value.appendString(p.scanner.TokenText())
282 p.accept(p.tok)
283 }
284 }
285
Colin Cross08693d22016-05-25 17:25:40 -0700286 return value, p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800287}
288
289func (p *parser) parseEscape() {
290 p.scanner.Mode = 0
291 p.accept('\\')
292 p.scanner.Mode = scanner.ScanIdents
293}
294
Colin Cross08693d22016-05-25 17:25:40 -0700295func (p *parser) parseExpression(end ...rune) *MakeString {
296 value := SimpleMakeString("", p.pos())
Colin Cross3f40fa42015-01-30 17:27:36 -0800297
298 endParen := false
299 for _, r := range end {
300 if r == ')' {
301 endParen = true
302 }
303 }
304 parens := 0
305
Colin Cross3f40fa42015-01-30 17:27:36 -0800306loop:
307 for {
308 if endParen && parens > 0 && p.tok == ')' {
309 parens--
310 value.appendString(")")
Colin Cross3f40fa42015-01-30 17:27:36 -0800311 p.accept(')')
312 continue
313 }
314
315 for _, r := range end {
316 if p.tok == r {
317 break loop
318 }
319 }
320
321 switch p.tok {
322 case '\n':
323 break loop
324 case scanner.Ident:
325 value.appendString(p.scanner.TokenText())
Colin Cross3f40fa42015-01-30 17:27:36 -0800326 p.accept(scanner.Ident)
327 case '\\':
328 p.parseEscape()
329 switch p.tok {
330 case '\n':
331 value.appendString(" ")
332 case scanner.EOF:
333 p.errorf("expected escaped character, found %s",
334 scanner.TokenString(p.tok))
Colin Cross08693d22016-05-25 17:25:40 -0700335 return value
Colin Cross3f40fa42015-01-30 17:27:36 -0800336 default:
337 value.appendString(`\` + string(p.tok))
338 }
Colin Cross3f40fa42015-01-30 17:27:36 -0800339 p.accept(p.tok)
340 case '#':
341 p.parseComment()
342 break loop
343 case '$':
344 var variable Variable
Colin Cross08693d22016-05-25 17:25:40 -0700345 variable = p.parseVariable()
Dan Willemsen43398532018-02-21 02:10:29 -0800346 if variable.Name == builtinDollarName {
347 value.appendString("$")
348 } else {
349 value.appendVariable(variable)
350 }
Colin Cross3f40fa42015-01-30 17:27:36 -0800351 case scanner.EOF:
352 break loop
353 case '(':
354 if endParen {
355 parens++
356 }
357 value.appendString("(")
Colin Cross3f40fa42015-01-30 17:27:36 -0800358 p.accept('(')
359 default:
360 value.appendString(p.scanner.TokenText())
Colin Cross3f40fa42015-01-30 17:27:36 -0800361 p.accept(p.tok)
362 }
363 }
364
365 if parens > 0 {
366 p.errorf("expected closing paren %s", value.Dump())
367 }
Colin Cross08693d22016-05-25 17:25:40 -0700368 return value
Colin Cross3f40fa42015-01-30 17:27:36 -0800369}
370
Colin Cross08693d22016-05-25 17:25:40 -0700371func (p *parser) parseVariable() Variable {
372 pos := p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800373 p.accept('$')
374 var name *MakeString
375 switch p.tok {
376 case '(':
377 return p.parseBracketedVariable('(', ')', pos)
378 case '{':
379 return p.parseBracketedVariable('{', '}', pos)
380 case '$':
Dan Willemsen43398532018-02-21 02:10:29 -0800381 name = builtinDollarName
382 p.accept(p.tok)
Colin Cross3f40fa42015-01-30 17:27:36 -0800383 case scanner.EOF:
384 p.errorf("expected variable name, found %s",
385 scanner.TokenString(p.tok))
386 default:
Colin Cross08693d22016-05-25 17:25:40 -0700387 name = p.parseExpression(variableNameEndRunes...)
Colin Cross3f40fa42015-01-30 17:27:36 -0800388 }
389
Colin Cross08693d22016-05-25 17:25:40 -0700390 return p.nameToVariable(name)
Colin Cross3f40fa42015-01-30 17:27:36 -0800391}
392
Colin Cross08693d22016-05-25 17:25:40 -0700393func (p *parser) parseBracketedVariable(start, end rune, pos Pos) Variable {
Colin Cross3f40fa42015-01-30 17:27:36 -0800394 p.accept(start)
Colin Cross08693d22016-05-25 17:25:40 -0700395 name := p.parseExpression(end)
Colin Cross3f40fa42015-01-30 17:27:36 -0800396 p.accept(end)
Colin Cross08693d22016-05-25 17:25:40 -0700397 return p.nameToVariable(name)
Colin Cross3f40fa42015-01-30 17:27:36 -0800398}
399
Colin Cross08693d22016-05-25 17:25:40 -0700400func (p *parser) nameToVariable(name *MakeString) Variable {
Colin Cross3f40fa42015-01-30 17:27:36 -0800401 return Variable{
Colin Cross3f40fa42015-01-30 17:27:36 -0800402 Name: name,
403 }
404}
405
406func (p *parser) parseRule(target *MakeString) {
407 prerequisites, newLine := p.parseRulePrerequisites(target)
408
409 recipe := ""
Colin Cross08693d22016-05-25 17:25:40 -0700410 recipePos := p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800411loop:
412 for {
413 if newLine {
414 if p.tok == '\t' {
Colin Cross3f40fa42015-01-30 17:27:36 -0800415 p.accept('\t')
416 newLine = false
417 continue loop
418 } else if p.parseDirective() {
419 newLine = false
420 continue
421 } else {
422 break loop
423 }
424 }
425
426 newLine = false
427 switch p.tok {
428 case '\\':
429 p.parseEscape()
430 recipe += string(p.tok)
Colin Cross3f40fa42015-01-30 17:27:36 -0800431 p.accept(p.tok)
432 case '\n':
433 newLine = true
434 recipe += "\n"
Colin Cross3f40fa42015-01-30 17:27:36 -0800435 p.accept('\n')
436 case scanner.EOF:
437 break loop
438 default:
439 recipe += p.scanner.TokenText()
Colin Cross3f40fa42015-01-30 17:27:36 -0800440 p.accept(p.tok)
441 }
442 }
443
444 if prerequisites != nil {
Colin Cross08693d22016-05-25 17:25:40 -0700445 p.nodes = append(p.nodes, &Rule{
Colin Cross3f40fa42015-01-30 17:27:36 -0800446 Target: target,
447 Prerequisites: prerequisites,
448 Recipe: recipe,
Colin Cross08693d22016-05-25 17:25:40 -0700449 RecipePos: recipePos,
Colin Cross3f40fa42015-01-30 17:27:36 -0800450 })
451 }
452}
453
454func (p *parser) parseRulePrerequisites(target *MakeString) (*MakeString, bool) {
455 newLine := false
456
457 p.ignoreSpaces()
458
Colin Cross08693d22016-05-25 17:25:40 -0700459 prerequisites := p.parseExpression('#', '\n', ';', ':', '=')
Colin Cross3f40fa42015-01-30 17:27:36 -0800460
461 switch p.tok {
462 case '\n':
463 p.accept('\n')
464 newLine = true
465 case '#':
466 p.parseComment()
467 newLine = true
468 case ';':
469 p.accept(';')
470 case ':':
471 p.accept(':')
472 if p.tok == '=' {
473 p.parseAssignment(":=", target, prerequisites)
474 return nil, true
475 } else {
Colin Cross08693d22016-05-25 17:25:40 -0700476 more := p.parseExpression('#', '\n', ';')
Colin Cross3f40fa42015-01-30 17:27:36 -0800477 prerequisites.appendMakeString(more)
478 }
479 case '=':
480 p.parseAssignment("=", target, prerequisites)
481 return nil, true
Dan Willemsen43398532018-02-21 02:10:29 -0800482 case scanner.EOF:
483 // do nothing
Colin Cross3f40fa42015-01-30 17:27:36 -0800484 default:
485 p.errorf("unexpected token %s after rule prerequisites", scanner.TokenString(p.tok))
486 }
487
488 return prerequisites, newLine
489}
490
491func (p *parser) parseComment() {
Colin Cross08693d22016-05-25 17:25:40 -0700492 pos := p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800493 p.accept('#')
494 comment := ""
Colin Cross3f40fa42015-01-30 17:27:36 -0800495loop:
496 for {
497 switch p.tok {
498 case '\\':
499 p.parseEscape()
500 if p.tok == '\n' {
Sasha Smundake10952b2019-03-05 17:34:32 -0800501 // Special case: '\' does not "escape" newline in comment (b/127521510)
502 comment += "\\"
503 p.accept(p.tok)
504 break loop
Colin Cross3f40fa42015-01-30 17:27:36 -0800505 }
Sasha Smundake10952b2019-03-05 17:34:32 -0800506 comment += "\\" + p.scanner.TokenText()
Colin Cross3f40fa42015-01-30 17:27:36 -0800507 p.accept(p.tok)
508 case '\n':
Colin Cross3f40fa42015-01-30 17:27:36 -0800509 p.accept('\n')
510 break loop
511 case scanner.EOF:
512 break loop
513 default:
514 comment += p.scanner.TokenText()
Colin Cross3f40fa42015-01-30 17:27:36 -0800515 p.accept(p.tok)
516 }
517 }
518
Colin Cross08693d22016-05-25 17:25:40 -0700519 p.comments = append(p.comments, &Comment{
520 CommentPos: pos,
521 Comment: comment,
Colin Cross3f40fa42015-01-30 17:27:36 -0800522 })
523}
524
525func (p *parser) parseAssignment(t string, target *MakeString, ident *MakeString) {
526 // The value of an assignment is everything including and after the first
527 // non-whitespace character after the = until the end of the logical line,
528 // which may included escaped newlines
529 p.accept('=')
Colin Cross08693d22016-05-25 17:25:40 -0700530 value := p.parseExpression()
Colin Cross3f40fa42015-01-30 17:27:36 -0800531 value.TrimLeftSpaces()
532 if ident.EndsWith('+') && t == "=" {
533 ident.TrimRightOne()
534 t = "+="
535 }
536
537 ident.TrimRightSpaces()
538
Colin Cross08693d22016-05-25 17:25:40 -0700539 p.nodes = append(p.nodes, &Assignment{
Colin Cross3f40fa42015-01-30 17:27:36 -0800540 Name: ident,
541 Value: value,
542 Target: target,
543 Type: t,
544 })
545}
546
547type androidMkModule struct {
548 assignments map[string]string
549}
550
551type androidMkFile struct {
552 assignments map[string]string
553 modules []androidMkModule
554 includes []string
555}
556
557var directives = [...]string{
558 "define",
559 "else",
560 "endef",
561 "endif",
562 "ifdef",
563 "ifeq",
564 "ifndef",
565 "ifneq",
566 "include",
567 "-include",
568}
569
570var functions = [...]string{
571 "abspath",
572 "addprefix",
573 "addsuffix",
574 "basename",
575 "dir",
576 "notdir",
577 "subst",
578 "suffix",
579 "filter",
580 "filter-out",
581 "findstring",
582 "firstword",
583 "flavor",
584 "join",
585 "lastword",
586 "patsubst",
587 "realpath",
588 "shell",
589 "sort",
590 "strip",
591 "wildcard",
592 "word",
593 "wordlist",
594 "words",
595 "origin",
596 "foreach",
597 "call",
598 "info",
599 "error",
600 "warning",
601 "if",
602 "or",
603 "and",
604 "value",
605 "eval",
606 "file",
607}
608
609func init() {
610 sort.Strings(directives[:])
611 sort.Strings(functions[:])
612}
613
614func isDirective(s string) bool {
615 for _, d := range directives {
616 if s == d {
617 return true
618 } else if s < d {
619 return false
620 }
621 }
622 return false
623}
624
625func isFunctionName(s string) bool {
626 for _, f := range functions {
627 if s == f {
628 return true
629 } else if s < f {
630 return false
631 }
632 }
633 return false
634}
635
636func isWhitespace(ch rune) bool {
637 return ch == ' ' || ch == '\t' || ch == '\n'
638}
639
640func isValidVariableRune(ch rune) bool {
641 return ch != scanner.Ident && ch != ':' && ch != '=' && ch != '#'
642}
643
644var whitespaceRunes = []rune{' ', '\t', '\n'}
645var variableNameEndRunes = append([]rune{':', '=', '#', ')', '}'}, whitespaceRunes...)
646
647func (p *parser) ignoreSpaces() int {
648 skipped := 0
649 for p.tok == ' ' || p.tok == '\t' {
650 p.accept(p.tok)
651 skipped++
652 }
653 return skipped
654}
655
656func (p *parser) ignoreWhitespace() {
657 for isWhitespace(p.tok) {
658 p.accept(p.tok)
659 }
660}