blob: f2477db3a9fb9ac2d83cf454eeabd48699bb488d [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,
Min Yune9cc4032024-09-03 20:36:47 +0900451 RecipeEndPos: p.pos(),
Colin Cross3f40fa42015-01-30 17:27:36 -0800452 })
453 }
454}
455
456func (p *parser) parseRulePrerequisites(target *MakeString) (*MakeString, bool) {
457 newLine := false
458
459 p.ignoreSpaces()
460
Colin Cross08693d22016-05-25 17:25:40 -0700461 prerequisites := p.parseExpression('#', '\n', ';', ':', '=')
Colin Cross3f40fa42015-01-30 17:27:36 -0800462
463 switch p.tok {
464 case '\n':
465 p.accept('\n')
466 newLine = true
467 case '#':
468 p.parseComment()
469 newLine = true
470 case ';':
471 p.accept(';')
472 case ':':
473 p.accept(':')
474 if p.tok == '=' {
475 p.parseAssignment(":=", target, prerequisites)
476 return nil, true
477 } else {
Colin Cross08693d22016-05-25 17:25:40 -0700478 more := p.parseExpression('#', '\n', ';')
Colin Cross3f40fa42015-01-30 17:27:36 -0800479 prerequisites.appendMakeString(more)
480 }
481 case '=':
482 p.parseAssignment("=", target, prerequisites)
483 return nil, true
Dan Willemsen43398532018-02-21 02:10:29 -0800484 case scanner.EOF:
485 // do nothing
Colin Cross3f40fa42015-01-30 17:27:36 -0800486 default:
487 p.errorf("unexpected token %s after rule prerequisites", scanner.TokenString(p.tok))
488 }
489
490 return prerequisites, newLine
491}
492
493func (p *parser) parseComment() {
Colin Cross08693d22016-05-25 17:25:40 -0700494 pos := p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800495 p.accept('#')
496 comment := ""
Colin Cross3f40fa42015-01-30 17:27:36 -0800497loop:
498 for {
499 switch p.tok {
500 case '\\':
501 p.parseEscape()
Sasha Smundake10952b2019-03-05 17:34:32 -0800502 comment += "\\" + p.scanner.TokenText()
Colin Cross3f40fa42015-01-30 17:27:36 -0800503 p.accept(p.tok)
504 case '\n':
Colin Cross3f40fa42015-01-30 17:27:36 -0800505 p.accept('\n')
506 break loop
507 case scanner.EOF:
508 break loop
509 default:
510 comment += p.scanner.TokenText()
Colin Cross3f40fa42015-01-30 17:27:36 -0800511 p.accept(p.tok)
512 }
513 }
514
Colin Cross08693d22016-05-25 17:25:40 -0700515 p.comments = append(p.comments, &Comment{
516 CommentPos: pos,
517 Comment: comment,
Colin Cross3f40fa42015-01-30 17:27:36 -0800518 })
519}
520
521func (p *parser) parseAssignment(t string, target *MakeString, ident *MakeString) {
522 // The value of an assignment is everything including and after the first
523 // non-whitespace character after the = until the end of the logical line,
524 // which may included escaped newlines
525 p.accept('=')
Cole Fauste309a912022-03-16 13:42:34 -0700526 value := p.parseExpression('#')
Colin Cross3f40fa42015-01-30 17:27:36 -0800527 value.TrimLeftSpaces()
528 if ident.EndsWith('+') && t == "=" {
529 ident.TrimRightOne()
530 t = "+="
531 }
532
533 ident.TrimRightSpaces()
534
Colin Cross08693d22016-05-25 17:25:40 -0700535 p.nodes = append(p.nodes, &Assignment{
Colin Cross3f40fa42015-01-30 17:27:36 -0800536 Name: ident,
537 Value: value,
538 Target: target,
539 Type: t,
540 })
541}
542
543type androidMkModule struct {
544 assignments map[string]string
545}
546
547type androidMkFile struct {
548 assignments map[string]string
549 modules []androidMkModule
550 includes []string
551}
552
553var directives = [...]string{
554 "define",
555 "else",
556 "endef",
557 "endif",
Sasha Smundakd63f7f02020-11-29 12:52:47 -0800558 "export",
Colin Cross3f40fa42015-01-30 17:27:36 -0800559 "ifdef",
560 "ifeq",
561 "ifndef",
562 "ifneq",
563 "include",
564 "-include",
Sasha Smundakd63f7f02020-11-29 12:52:47 -0800565 "unexport",
Colin Cross3f40fa42015-01-30 17:27:36 -0800566}
567
568var functions = [...]string{
569 "abspath",
570 "addprefix",
571 "addsuffix",
572 "basename",
573 "dir",
574 "notdir",
575 "subst",
576 "suffix",
577 "filter",
578 "filter-out",
579 "findstring",
580 "firstword",
581 "flavor",
582 "join",
583 "lastword",
584 "patsubst",
585 "realpath",
586 "shell",
587 "sort",
588 "strip",
589 "wildcard",
590 "word",
591 "wordlist",
592 "words",
593 "origin",
594 "foreach",
595 "call",
596 "info",
597 "error",
598 "warning",
599 "if",
600 "or",
601 "and",
602 "value",
603 "eval",
604 "file",
605}
606
607func init() {
608 sort.Strings(directives[:])
609 sort.Strings(functions[:])
610}
611
612func isDirective(s string) bool {
613 for _, d := range directives {
614 if s == d {
615 return true
616 } else if s < d {
617 return false
618 }
619 }
620 return false
621}
622
623func isFunctionName(s string) bool {
624 for _, f := range functions {
625 if s == f {
626 return true
627 } else if s < f {
628 return false
629 }
630 }
631 return false
632}
633
634func isWhitespace(ch rune) bool {
635 return ch == ' ' || ch == '\t' || ch == '\n'
636}
637
638func isValidVariableRune(ch rune) bool {
639 return ch != scanner.Ident && ch != ':' && ch != '=' && ch != '#'
640}
641
642var whitespaceRunes = []rune{' ', '\t', '\n'}
643var variableNameEndRunes = append([]rune{':', '=', '#', ')', '}'}, whitespaceRunes...)
644
645func (p *parser) ignoreSpaces() int {
646 skipped := 0
647 for p.tok == ' ' || p.tok == '\t' {
648 p.accept(p.tok)
649 skipped++
650 }
651 return skipped
652}
653
654func (p *parser) ignoreWhitespace() {
655 for isWhitespace(p.tok) {
656 p.accept(p.tok)
657 }
658}