blob: 8a20bb0524e3a0088bd0ae6828f76264cdd257c2 [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
Min Yun53ca8b22024-02-23 23:16:14 +0900416 } else if p.tok == '\n' {
417 p.accept('\n')
418 continue loop
Colin Cross3f40fa42015-01-30 17:27:36 -0800419 } else if p.parseDirective() {
420 newLine = false
421 continue
422 } else {
423 break loop
424 }
425 }
426
427 newLine = false
428 switch p.tok {
429 case '\\':
430 p.parseEscape()
431 recipe += string(p.tok)
Colin Cross3f40fa42015-01-30 17:27:36 -0800432 p.accept(p.tok)
433 case '\n':
434 newLine = true
435 recipe += "\n"
Colin Cross3f40fa42015-01-30 17:27:36 -0800436 p.accept('\n')
437 case scanner.EOF:
438 break loop
439 default:
440 recipe += p.scanner.TokenText()
Colin Cross3f40fa42015-01-30 17:27:36 -0800441 p.accept(p.tok)
442 }
443 }
444
445 if prerequisites != nil {
Colin Cross08693d22016-05-25 17:25:40 -0700446 p.nodes = append(p.nodes, &Rule{
Colin Cross3f40fa42015-01-30 17:27:36 -0800447 Target: target,
448 Prerequisites: prerequisites,
449 Recipe: recipe,
Colin Cross08693d22016-05-25 17:25:40 -0700450 RecipePos: recipePos,
Colin Cross3f40fa42015-01-30 17:27:36 -0800451 })
452 }
453}
454
455func (p *parser) parseRulePrerequisites(target *MakeString) (*MakeString, bool) {
456 newLine := false
457
458 p.ignoreSpaces()
459
Colin Cross08693d22016-05-25 17:25:40 -0700460 prerequisites := p.parseExpression('#', '\n', ';', ':', '=')
Colin Cross3f40fa42015-01-30 17:27:36 -0800461
462 switch p.tok {
463 case '\n':
464 p.accept('\n')
465 newLine = true
466 case '#':
467 p.parseComment()
468 newLine = true
469 case ';':
470 p.accept(';')
471 case ':':
472 p.accept(':')
473 if p.tok == '=' {
474 p.parseAssignment(":=", target, prerequisites)
475 return nil, true
476 } else {
Colin Cross08693d22016-05-25 17:25:40 -0700477 more := p.parseExpression('#', '\n', ';')
Colin Cross3f40fa42015-01-30 17:27:36 -0800478 prerequisites.appendMakeString(more)
479 }
480 case '=':
481 p.parseAssignment("=", target, prerequisites)
482 return nil, true
Dan Willemsen43398532018-02-21 02:10:29 -0800483 case scanner.EOF:
484 // do nothing
Colin Cross3f40fa42015-01-30 17:27:36 -0800485 default:
486 p.errorf("unexpected token %s after rule prerequisites", scanner.TokenString(p.tok))
487 }
488
489 return prerequisites, newLine
490}
491
492func (p *parser) parseComment() {
Colin Cross08693d22016-05-25 17:25:40 -0700493 pos := p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800494 p.accept('#')
495 comment := ""
Colin Cross3f40fa42015-01-30 17:27:36 -0800496loop:
497 for {
498 switch p.tok {
499 case '\\':
500 p.parseEscape()
Sasha Smundake10952b2019-03-05 17:34:32 -0800501 comment += "\\" + p.scanner.TokenText()
Colin Cross3f40fa42015-01-30 17:27:36 -0800502 p.accept(p.tok)
503 case '\n':
Colin Cross3f40fa42015-01-30 17:27:36 -0800504 p.accept('\n')
505 break loop
506 case scanner.EOF:
507 break loop
508 default:
509 comment += p.scanner.TokenText()
Colin Cross3f40fa42015-01-30 17:27:36 -0800510 p.accept(p.tok)
511 }
512 }
513
Colin Cross08693d22016-05-25 17:25:40 -0700514 p.comments = append(p.comments, &Comment{
515 CommentPos: pos,
516 Comment: comment,
Colin Cross3f40fa42015-01-30 17:27:36 -0800517 })
518}
519
520func (p *parser) parseAssignment(t string, target *MakeString, ident *MakeString) {
521 // The value of an assignment is everything including and after the first
522 // non-whitespace character after the = until the end of the logical line,
523 // which may included escaped newlines
524 p.accept('=')
Cole Fauste309a912022-03-16 13:42:34 -0700525 value := p.parseExpression('#')
Colin Cross3f40fa42015-01-30 17:27:36 -0800526 value.TrimLeftSpaces()
527 if ident.EndsWith('+') && t == "=" {
528 ident.TrimRightOne()
529 t = "+="
530 }
531
532 ident.TrimRightSpaces()
533
Colin Cross08693d22016-05-25 17:25:40 -0700534 p.nodes = append(p.nodes, &Assignment{
Colin Cross3f40fa42015-01-30 17:27:36 -0800535 Name: ident,
536 Value: value,
537 Target: target,
538 Type: t,
539 })
540}
541
542type androidMkModule struct {
543 assignments map[string]string
544}
545
546type androidMkFile struct {
547 assignments map[string]string
548 modules []androidMkModule
549 includes []string
550}
551
552var directives = [...]string{
553 "define",
554 "else",
555 "endef",
556 "endif",
Sasha Smundakd63f7f02020-11-29 12:52:47 -0800557 "export",
Colin Cross3f40fa42015-01-30 17:27:36 -0800558 "ifdef",
559 "ifeq",
560 "ifndef",
561 "ifneq",
562 "include",
563 "-include",
Sasha Smundakd63f7f02020-11-29 12:52:47 -0800564 "unexport",
Colin Cross3f40fa42015-01-30 17:27:36 -0800565}
566
567var functions = [...]string{
568 "abspath",
569 "addprefix",
570 "addsuffix",
571 "basename",
572 "dir",
573 "notdir",
574 "subst",
575 "suffix",
576 "filter",
577 "filter-out",
578 "findstring",
579 "firstword",
580 "flavor",
581 "join",
582 "lastword",
583 "patsubst",
584 "realpath",
585 "shell",
586 "sort",
587 "strip",
588 "wildcard",
589 "word",
590 "wordlist",
591 "words",
592 "origin",
593 "foreach",
594 "call",
595 "info",
596 "error",
597 "warning",
598 "if",
599 "or",
600 "and",
601 "value",
602 "eval",
603 "file",
604}
605
606func init() {
607 sort.Strings(directives[:])
608 sort.Strings(functions[:])
609}
610
611func isDirective(s string) bool {
612 for _, d := range directives {
613 if s == d {
614 return true
615 } else if s < d {
616 return false
617 }
618 }
619 return false
620}
621
622func isFunctionName(s string) bool {
623 for _, f := range functions {
624 if s == f {
625 return true
626 } else if s < f {
627 return false
628 }
629 }
630 return false
631}
632
633func isWhitespace(ch rune) bool {
634 return ch == ' ' || ch == '\t' || ch == '\n'
635}
636
637func isValidVariableRune(ch rune) bool {
638 return ch != scanner.Ident && ch != ':' && ch != '=' && ch != '#'
639}
640
641var whitespaceRunes = []rune{' ', '\t', '\n'}
642var variableNameEndRunes = append([]rune{':', '=', '#', ')', '}'}, whitespaceRunes...)
643
644func (p *parser) ignoreSpaces() int {
645 skipped := 0
646 for p.tok == ' ' || p.tok == '\t' {
647 p.accept(p.tok)
648 skipped++
649 }
650 return skipped
651}
652
653func (p *parser) ignoreWhitespace() {
654 for isWhitespace(p.tok) {
655 p.accept(p.tok)
656 }
657}