blob: ef5b492246441ead960cda5bde4c8518c4eb6e34 [file] [log] [blame]
Colin Cross3f40fa42015-01-30 17:27:36 -08001package parser
2
3import (
4 "errors"
5 "fmt"
6 "io"
7 "sort"
8 "text/scanner"
9)
10
11var errTooManyErrors = errors.New("too many errors")
12
13const maxErrors = 100
14
15type ParseError struct {
16 Err error
17 Pos scanner.Position
18}
19
20func (e *ParseError) Error() string {
21 return fmt.Sprintf("%s: %s", e.Pos, e.Err)
22}
23
Colin Cross08693d22016-05-25 17:25:40 -070024func (p *parser) Parse() ([]Node, []error) {
Colin Cross3f40fa42015-01-30 17:27:36 -080025 defer func() {
26 if r := recover(); r != nil {
27 if r == errTooManyErrors {
28 return
29 }
30 panic(r)
31 }
32 }()
33
34 p.parseLines()
35 p.accept(scanner.EOF)
Colin Cross08693d22016-05-25 17:25:40 -070036 p.nodes = append(p.nodes, p.comments...)
37 sort.Sort(byPosition(p.nodes))
Colin Cross3f40fa42015-01-30 17:27:36 -080038
Colin Cross08693d22016-05-25 17:25:40 -070039 return p.nodes, p.errors
Colin Cross3f40fa42015-01-30 17:27:36 -080040}
41
42type parser struct {
43 scanner scanner.Scanner
44 tok rune
45 errors []error
Colin Cross08693d22016-05-25 17:25:40 -070046 comments []Node
47 nodes []Node
48 lines []int
Colin Cross3f40fa42015-01-30 17:27:36 -080049}
50
51func NewParser(filename string, r io.Reader) *parser {
52 p := &parser{}
Colin Cross08693d22016-05-25 17:25:40 -070053 p.lines = []int{0}
Colin Cross3f40fa42015-01-30 17:27:36 -080054 p.scanner.Init(r)
55 p.scanner.Error = func(sc *scanner.Scanner, msg string) {
56 p.errorf(msg)
57 }
58 p.scanner.Whitespace = 0
59 p.scanner.IsIdentRune = func(ch rune, i int) bool {
60 return ch > 0 && ch != ':' && ch != '#' && ch != '=' && ch != '+' && ch != '$' &&
61 ch != '\\' && ch != '(' && ch != ')' && ch != '{' && ch != '}' && ch != ';' &&
62 ch != '|' && ch != '?' && ch != '\r' && !isWhitespace(ch)
63 }
64 p.scanner.Mode = scanner.ScanIdents
65 p.scanner.Filename = filename
66 p.next()
67 return p
68}
69
Colin Cross08693d22016-05-25 17:25:40 -070070func (p *parser) Unpack(pos Pos) scanner.Position {
71 offset := int(pos)
72 line := sort.Search(len(p.lines), func(i int) bool { return p.lines[i] > offset }) - 1
73 return scanner.Position{
74 Filename: p.scanner.Filename,
75 Line: line + 1,
76 Column: offset - p.lines[line] + 1,
77 Offset: offset,
78 }
79}
80
81func (p *parser) pos() Pos {
Colin Cross3f40fa42015-01-30 17:27:36 -080082 pos := p.scanner.Position
83 if !pos.IsValid() {
84 pos = p.scanner.Pos()
85 }
Colin Cross08693d22016-05-25 17:25:40 -070086 return Pos(pos.Offset)
87}
88
89func (p *parser) errorf(format string, args ...interface{}) {
Colin Cross3f40fa42015-01-30 17:27:36 -080090 err := &ParseError{
91 Err: fmt.Errorf(format, args...),
Colin Cross08693d22016-05-25 17:25:40 -070092 Pos: p.scanner.Position,
Colin Cross3f40fa42015-01-30 17:27:36 -080093 }
94 p.errors = append(p.errors, err)
95 if len(p.errors) >= maxErrors {
96 panic(errTooManyErrors)
97 }
98}
99
100func (p *parser) accept(toks ...rune) bool {
101 for _, tok := range toks {
102 if p.tok != tok {
103 p.errorf("expected %s, found %s", scanner.TokenString(tok),
104 scanner.TokenString(p.tok))
105 return false
106 }
107 p.next()
108 }
109 return true
110}
111
112func (p *parser) next() {
113 if p.tok != scanner.EOF {
114 p.tok = p.scanner.Scan()
115 for p.tok == '\r' {
116 p.tok = p.scanner.Scan()
117 }
118 }
Colin Cross08693d22016-05-25 17:25:40 -0700119 if p.tok == '\n' {
120 p.lines = append(p.lines, p.scanner.Position.Offset+1)
121 }
Colin Cross3f40fa42015-01-30 17:27:36 -0800122}
123
124func (p *parser) parseLines() {
125 for {
126 p.ignoreWhitespace()
127
128 if p.parseDirective() {
129 continue
130 }
131
Colin Cross08693d22016-05-25 17:25:40 -0700132 ident := p.parseExpression('=', '?', ':', '#', '\n')
Colin Cross3f40fa42015-01-30 17:27:36 -0800133
134 p.ignoreSpaces()
135
136 switch p.tok {
137 case '?':
138 p.accept('?')
139 if p.tok == '=' {
140 p.parseAssignment("?=", nil, ident)
141 } else {
142 p.errorf("expected = after ?")
143 }
144 case '+':
145 p.accept('+')
146 if p.tok == '=' {
147 p.parseAssignment("+=", nil, ident)
148 } else {
149 p.errorf("expected = after +")
150 }
151 case ':':
152 p.accept(':')
153 switch p.tok {
154 case '=':
155 p.parseAssignment(":=", nil, ident)
156 default:
157 p.parseRule(ident)
158 }
159 case '=':
160 p.parseAssignment("=", nil, ident)
161 case '#', '\n', scanner.EOF:
162 ident.TrimRightSpaces()
163 if v, ok := toVariable(ident); ok {
Colin Cross08693d22016-05-25 17:25:40 -0700164 p.nodes = append(p.nodes, &v)
Colin Cross3f40fa42015-01-30 17:27:36 -0800165 } else if !ident.Empty() {
166 p.errorf("expected directive, rule, or assignment after ident " + ident.Dump())
167 }
168 switch p.tok {
169 case scanner.EOF:
170 return
171 case '\n':
172 p.accept('\n')
173 case '#':
174 p.parseComment()
175 }
176 default:
177 p.errorf("expected assignment or rule definition, found %s\n",
178 p.scanner.TokenText())
179 return
180 }
181 }
182}
183
184func (p *parser) parseDirective() bool {
185 if p.tok != scanner.Ident || !isDirective(p.scanner.TokenText()) {
186 return false
187 }
188
189 d := p.scanner.TokenText()
Colin Cross08693d22016-05-25 17:25:40 -0700190 pos := p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800191 p.accept(scanner.Ident)
Colin Cross08693d22016-05-25 17:25:40 -0700192 endPos := NoPos
Colin Cross3f40fa42015-01-30 17:27:36 -0800193
194 expression := SimpleMakeString("", pos)
195
196 switch d {
197 case "endif", "endef", "else":
198 // Nothing
199 case "define":
Colin Cross08693d22016-05-25 17:25:40 -0700200 expression, endPos = p.parseDefine()
Colin Cross3f40fa42015-01-30 17:27:36 -0800201 default:
202 p.ignoreSpaces()
Colin Cross08693d22016-05-25 17:25:40 -0700203 expression = p.parseExpression()
Colin Cross3f40fa42015-01-30 17:27:36 -0800204 }
205
Colin Cross08693d22016-05-25 17:25:40 -0700206 p.nodes = append(p.nodes, &Directive{
207 NamePos: pos,
208 Name: d,
209 Args: expression,
210 EndPos: endPos,
Colin Cross3f40fa42015-01-30 17:27:36 -0800211 })
212 return true
213}
214
Colin Cross08693d22016-05-25 17:25:40 -0700215func (p *parser) parseDefine() (*MakeString, Pos) {
216 value := SimpleMakeString("", p.pos())
Colin Cross3f40fa42015-01-30 17:27:36 -0800217
218loop:
219 for {
220 switch p.tok {
221 case scanner.Ident:
Colin Cross08693d22016-05-25 17:25:40 -0700222 value.appendString(p.scanner.TokenText())
Colin Cross3f40fa42015-01-30 17:27:36 -0800223 if p.scanner.TokenText() == "endef" {
224 p.accept(scanner.Ident)
225 break loop
226 }
Colin Cross3f40fa42015-01-30 17:27:36 -0800227 p.accept(scanner.Ident)
228 case '\\':
229 p.parseEscape()
230 switch p.tok {
231 case '\n':
232 value.appendString(" ")
233 case scanner.EOF:
234 p.errorf("expected escaped character, found %s",
235 scanner.TokenString(p.tok))
236 break loop
237 default:
238 value.appendString(`\` + string(p.tok))
239 }
240 p.accept(p.tok)
241 //TODO: handle variables inside defines? result depends if
242 //define is used in make or rule context
243 //case '$':
244 // variable := p.parseVariable()
245 // value.appendVariable(variable)
246 case scanner.EOF:
247 p.errorf("unexpected EOF while looking for endef")
248 break loop
249 default:
250 value.appendString(p.scanner.TokenText())
251 p.accept(p.tok)
252 }
253 }
254
Colin Cross08693d22016-05-25 17:25:40 -0700255 return value, p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800256}
257
258func (p *parser) parseEscape() {
259 p.scanner.Mode = 0
260 p.accept('\\')
261 p.scanner.Mode = scanner.ScanIdents
262}
263
Colin Cross08693d22016-05-25 17:25:40 -0700264func (p *parser) parseExpression(end ...rune) *MakeString {
265 value := SimpleMakeString("", p.pos())
Colin Cross3f40fa42015-01-30 17:27:36 -0800266
267 endParen := false
268 for _, r := range end {
269 if r == ')' {
270 endParen = true
271 }
272 }
273 parens := 0
274
Colin Cross3f40fa42015-01-30 17:27:36 -0800275loop:
276 for {
277 if endParen && parens > 0 && p.tok == ')' {
278 parens--
279 value.appendString(")")
Colin Cross3f40fa42015-01-30 17:27:36 -0800280 p.accept(')')
281 continue
282 }
283
284 for _, r := range end {
285 if p.tok == r {
286 break loop
287 }
288 }
289
290 switch p.tok {
291 case '\n':
292 break loop
293 case scanner.Ident:
294 value.appendString(p.scanner.TokenText())
Colin Cross3f40fa42015-01-30 17:27:36 -0800295 p.accept(scanner.Ident)
296 case '\\':
297 p.parseEscape()
298 switch p.tok {
299 case '\n':
300 value.appendString(" ")
301 case scanner.EOF:
302 p.errorf("expected escaped character, found %s",
303 scanner.TokenString(p.tok))
Colin Cross08693d22016-05-25 17:25:40 -0700304 return value
Colin Cross3f40fa42015-01-30 17:27:36 -0800305 default:
306 value.appendString(`\` + string(p.tok))
307 }
Colin Cross3f40fa42015-01-30 17:27:36 -0800308 p.accept(p.tok)
309 case '#':
310 p.parseComment()
311 break loop
312 case '$':
313 var variable Variable
Colin Cross08693d22016-05-25 17:25:40 -0700314 variable = p.parseVariable()
Colin Cross3f40fa42015-01-30 17:27:36 -0800315 value.appendVariable(variable)
316 case scanner.EOF:
317 break loop
318 case '(':
319 if endParen {
320 parens++
321 }
322 value.appendString("(")
Colin Cross3f40fa42015-01-30 17:27:36 -0800323 p.accept('(')
324 default:
325 value.appendString(p.scanner.TokenText())
Colin Cross3f40fa42015-01-30 17:27:36 -0800326 p.accept(p.tok)
327 }
328 }
329
330 if parens > 0 {
331 p.errorf("expected closing paren %s", value.Dump())
332 }
Colin Cross08693d22016-05-25 17:25:40 -0700333 return value
Colin Cross3f40fa42015-01-30 17:27:36 -0800334}
335
Colin Cross08693d22016-05-25 17:25:40 -0700336func (p *parser) parseVariable() Variable {
337 pos := p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800338 p.accept('$')
339 var name *MakeString
340 switch p.tok {
341 case '(':
342 return p.parseBracketedVariable('(', ')', pos)
343 case '{':
344 return p.parseBracketedVariable('{', '}', pos)
345 case '$':
Colin Cross08693d22016-05-25 17:25:40 -0700346 name = SimpleMakeString("__builtin_dollar", NoPos)
Colin Cross3f40fa42015-01-30 17:27:36 -0800347 case scanner.EOF:
348 p.errorf("expected variable name, found %s",
349 scanner.TokenString(p.tok))
350 default:
Colin Cross08693d22016-05-25 17:25:40 -0700351 name = p.parseExpression(variableNameEndRunes...)
Colin Cross3f40fa42015-01-30 17:27:36 -0800352 }
353
Colin Cross08693d22016-05-25 17:25:40 -0700354 return p.nameToVariable(name)
Colin Cross3f40fa42015-01-30 17:27:36 -0800355}
356
Colin Cross08693d22016-05-25 17:25:40 -0700357func (p *parser) parseBracketedVariable(start, end rune, pos Pos) Variable {
Colin Cross3f40fa42015-01-30 17:27:36 -0800358 p.accept(start)
Colin Cross08693d22016-05-25 17:25:40 -0700359 name := p.parseExpression(end)
Colin Cross3f40fa42015-01-30 17:27:36 -0800360 p.accept(end)
Colin Cross08693d22016-05-25 17:25:40 -0700361 return p.nameToVariable(name)
Colin Cross3f40fa42015-01-30 17:27:36 -0800362}
363
Colin Cross08693d22016-05-25 17:25:40 -0700364func (p *parser) nameToVariable(name *MakeString) Variable {
Colin Cross3f40fa42015-01-30 17:27:36 -0800365 return Variable{
Colin Cross3f40fa42015-01-30 17:27:36 -0800366 Name: name,
367 }
368}
369
370func (p *parser) parseRule(target *MakeString) {
371 prerequisites, newLine := p.parseRulePrerequisites(target)
372
373 recipe := ""
Colin Cross08693d22016-05-25 17:25:40 -0700374 recipePos := p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800375loop:
376 for {
377 if newLine {
378 if p.tok == '\t' {
Colin Cross3f40fa42015-01-30 17:27:36 -0800379 p.accept('\t')
380 newLine = false
381 continue loop
382 } else if p.parseDirective() {
383 newLine = false
384 continue
385 } else {
386 break loop
387 }
388 }
389
390 newLine = false
391 switch p.tok {
392 case '\\':
393 p.parseEscape()
394 recipe += string(p.tok)
Colin Cross3f40fa42015-01-30 17:27:36 -0800395 p.accept(p.tok)
396 case '\n':
397 newLine = true
398 recipe += "\n"
Colin Cross3f40fa42015-01-30 17:27:36 -0800399 p.accept('\n')
400 case scanner.EOF:
401 break loop
402 default:
403 recipe += p.scanner.TokenText()
Colin Cross3f40fa42015-01-30 17:27:36 -0800404 p.accept(p.tok)
405 }
406 }
407
408 if prerequisites != nil {
Colin Cross08693d22016-05-25 17:25:40 -0700409 p.nodes = append(p.nodes, &Rule{
Colin Cross3f40fa42015-01-30 17:27:36 -0800410 Target: target,
411 Prerequisites: prerequisites,
412 Recipe: recipe,
Colin Cross08693d22016-05-25 17:25:40 -0700413 RecipePos: recipePos,
Colin Cross3f40fa42015-01-30 17:27:36 -0800414 })
415 }
416}
417
418func (p *parser) parseRulePrerequisites(target *MakeString) (*MakeString, bool) {
419 newLine := false
420
421 p.ignoreSpaces()
422
Colin Cross08693d22016-05-25 17:25:40 -0700423 prerequisites := p.parseExpression('#', '\n', ';', ':', '=')
Colin Cross3f40fa42015-01-30 17:27:36 -0800424
425 switch p.tok {
426 case '\n':
427 p.accept('\n')
428 newLine = true
429 case '#':
430 p.parseComment()
431 newLine = true
432 case ';':
433 p.accept(';')
434 case ':':
435 p.accept(':')
436 if p.tok == '=' {
437 p.parseAssignment(":=", target, prerequisites)
438 return nil, true
439 } else {
Colin Cross08693d22016-05-25 17:25:40 -0700440 more := p.parseExpression('#', '\n', ';')
Colin Cross3f40fa42015-01-30 17:27:36 -0800441 prerequisites.appendMakeString(more)
442 }
443 case '=':
444 p.parseAssignment("=", target, prerequisites)
445 return nil, true
446 default:
447 p.errorf("unexpected token %s after rule prerequisites", scanner.TokenString(p.tok))
448 }
449
450 return prerequisites, newLine
451}
452
453func (p *parser) parseComment() {
Colin Cross08693d22016-05-25 17:25:40 -0700454 pos := p.pos()
Colin Cross3f40fa42015-01-30 17:27:36 -0800455 p.accept('#')
456 comment := ""
Colin Cross3f40fa42015-01-30 17:27:36 -0800457loop:
458 for {
459 switch p.tok {
460 case '\\':
461 p.parseEscape()
462 if p.tok == '\n' {
463 comment += "\n"
464 } else {
465 comment += "\\" + p.scanner.TokenText()
466 }
Colin Cross3f40fa42015-01-30 17:27:36 -0800467 p.accept(p.tok)
468 case '\n':
Colin Cross3f40fa42015-01-30 17:27:36 -0800469 p.accept('\n')
470 break loop
471 case scanner.EOF:
472 break loop
473 default:
474 comment += p.scanner.TokenText()
Colin Cross3f40fa42015-01-30 17:27:36 -0800475 p.accept(p.tok)
476 }
477 }
478
Colin Cross08693d22016-05-25 17:25:40 -0700479 p.comments = append(p.comments, &Comment{
480 CommentPos: pos,
481 Comment: comment,
Colin Cross3f40fa42015-01-30 17:27:36 -0800482 })
483}
484
485func (p *parser) parseAssignment(t string, target *MakeString, ident *MakeString) {
486 // The value of an assignment is everything including and after the first
487 // non-whitespace character after the = until the end of the logical line,
488 // which may included escaped newlines
489 p.accept('=')
Colin Cross08693d22016-05-25 17:25:40 -0700490 value := p.parseExpression()
Colin Cross3f40fa42015-01-30 17:27:36 -0800491 value.TrimLeftSpaces()
492 if ident.EndsWith('+') && t == "=" {
493 ident.TrimRightOne()
494 t = "+="
495 }
496
497 ident.TrimRightSpaces()
498
Colin Cross08693d22016-05-25 17:25:40 -0700499 p.nodes = append(p.nodes, &Assignment{
Colin Cross3f40fa42015-01-30 17:27:36 -0800500 Name: ident,
501 Value: value,
502 Target: target,
503 Type: t,
504 })
505}
506
507type androidMkModule struct {
508 assignments map[string]string
509}
510
511type androidMkFile struct {
512 assignments map[string]string
513 modules []androidMkModule
514 includes []string
515}
516
517var directives = [...]string{
518 "define",
519 "else",
520 "endef",
521 "endif",
522 "ifdef",
523 "ifeq",
524 "ifndef",
525 "ifneq",
526 "include",
527 "-include",
528}
529
530var functions = [...]string{
531 "abspath",
532 "addprefix",
533 "addsuffix",
534 "basename",
535 "dir",
536 "notdir",
537 "subst",
538 "suffix",
539 "filter",
540 "filter-out",
541 "findstring",
542 "firstword",
543 "flavor",
544 "join",
545 "lastword",
546 "patsubst",
547 "realpath",
548 "shell",
549 "sort",
550 "strip",
551 "wildcard",
552 "word",
553 "wordlist",
554 "words",
555 "origin",
556 "foreach",
557 "call",
558 "info",
559 "error",
560 "warning",
561 "if",
562 "or",
563 "and",
564 "value",
565 "eval",
566 "file",
567}
568
569func init() {
570 sort.Strings(directives[:])
571 sort.Strings(functions[:])
572}
573
574func isDirective(s string) bool {
575 for _, d := range directives {
576 if s == d {
577 return true
578 } else if s < d {
579 return false
580 }
581 }
582 return false
583}
584
585func isFunctionName(s string) bool {
586 for _, f := range functions {
587 if s == f {
588 return true
589 } else if s < f {
590 return false
591 }
592 }
593 return false
594}
595
596func isWhitespace(ch rune) bool {
597 return ch == ' ' || ch == '\t' || ch == '\n'
598}
599
600func isValidVariableRune(ch rune) bool {
601 return ch != scanner.Ident && ch != ':' && ch != '=' && ch != '#'
602}
603
604var whitespaceRunes = []rune{' ', '\t', '\n'}
605var variableNameEndRunes = append([]rune{':', '=', '#', ')', '}'}, whitespaceRunes...)
606
607func (p *parser) ignoreSpaces() int {
608 skipped := 0
609 for p.tok == ' ' || p.tok == '\t' {
610 p.accept(p.tok)
611 skipped++
612 }
613 return skipped
614}
615
616func (p *parser) ignoreWhitespace() {
617 for isWhitespace(p.tok) {
618 p.accept(p.tok)
619 }
620}