Home | History | Annotate | Download | only in parser
      1 package parser
      2 
      3 import (
      4 	"errors"
      5 	"fmt"
      6 	"io"
      7 	"sort"
      8 	"text/scanner"
      9 )
     10 
     11 var errTooManyErrors = errors.New("too many errors")
     12 
     13 const maxErrors = 100
     14 
     15 type ParseError struct {
     16 	Err error
     17 	Pos scanner.Position
     18 }
     19 
     20 func (e *ParseError) Error() string {
     21 	return fmt.Sprintf("%s: %s", e.Pos, e.Err)
     22 }
     23 
     24 func (p *parser) Parse() ([]Node, []error) {
     25 	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)
     36 	p.nodes = append(p.nodes, p.comments...)
     37 	sort.Sort(byPosition(p.nodes))
     38 
     39 	return p.nodes, p.errors
     40 }
     41 
     42 type parser struct {
     43 	scanner  scanner.Scanner
     44 	tok      rune
     45 	errors   []error
     46 	comments []Node
     47 	nodes    []Node
     48 	lines    []int
     49 }
     50 
     51 func NewParser(filename string, r io.Reader) *parser {
     52 	p := &parser{}
     53 	p.lines = []int{0}
     54 	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 
     70 func (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 
     81 func (p *parser) pos() Pos {
     82 	pos := p.scanner.Position
     83 	if !pos.IsValid() {
     84 		pos = p.scanner.Pos()
     85 	}
     86 	return Pos(pos.Offset)
     87 }
     88 
     89 func (p *parser) errorf(format string, args ...interface{}) {
     90 	err := &ParseError{
     91 		Err: fmt.Errorf(format, args...),
     92 		Pos: p.scanner.Position,
     93 	}
     94 	p.errors = append(p.errors, err)
     95 	if len(p.errors) >= maxErrors {
     96 		panic(errTooManyErrors)
     97 	}
     98 }
     99 
    100 func (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 
    112 func (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 	}
    119 	if p.tok == '\n' {
    120 		p.lines = append(p.lines, p.scanner.Position.Offset+1)
    121 	}
    122 }
    123 
    124 func (p *parser) parseLines() {
    125 	for {
    126 		p.ignoreWhitespace()
    127 
    128 		if p.parseDirective() {
    129 			continue
    130 		}
    131 
    132 		ident := p.parseExpression('=', '?', ':', '#', '\n')
    133 
    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 {
    164 				p.nodes = append(p.nodes, &v)
    165 			} 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 
    184 func (p *parser) parseDirective() bool {
    185 	if p.tok != scanner.Ident || !isDirective(p.scanner.TokenText()) {
    186 		return false
    187 	}
    188 
    189 	d := p.scanner.TokenText()
    190 	pos := p.pos()
    191 	p.accept(scanner.Ident)
    192 	endPos := NoPos
    193 
    194 	expression := SimpleMakeString("", pos)
    195 
    196 	switch d {
    197 	case "endif", "endef", "else":
    198 		// Nothing
    199 	case "define":
    200 		expression, endPos = p.parseDefine()
    201 	default:
    202 		p.ignoreSpaces()
    203 		expression = p.parseExpression()
    204 	}
    205 
    206 	p.nodes = append(p.nodes, &Directive{
    207 		NamePos: pos,
    208 		Name:    d,
    209 		Args:    expression,
    210 		EndPos:  endPos,
    211 	})
    212 	return true
    213 }
    214 
    215 func (p *parser) parseDefine() (*MakeString, Pos) {
    216 	value := SimpleMakeString("", p.pos())
    217 
    218 loop:
    219 	for {
    220 		switch p.tok {
    221 		case scanner.Ident:
    222 			value.appendString(p.scanner.TokenText())
    223 			if p.scanner.TokenText() == "endef" {
    224 				p.accept(scanner.Ident)
    225 				break loop
    226 			}
    227 			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 
    255 	return value, p.pos()
    256 }
    257 
    258 func (p *parser) parseEscape() {
    259 	p.scanner.Mode = 0
    260 	p.accept('\\')
    261 	p.scanner.Mode = scanner.ScanIdents
    262 }
    263 
    264 func (p *parser) parseExpression(end ...rune) *MakeString {
    265 	value := SimpleMakeString("", p.pos())
    266 
    267 	endParen := false
    268 	for _, r := range end {
    269 		if r == ')' {
    270 			endParen = true
    271 		}
    272 	}
    273 	parens := 0
    274 
    275 loop:
    276 	for {
    277 		if endParen && parens > 0 && p.tok == ')' {
    278 			parens--
    279 			value.appendString(")")
    280 			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())
    295 			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))
    304 				return value
    305 			default:
    306 				value.appendString(`\` + string(p.tok))
    307 			}
    308 			p.accept(p.tok)
    309 		case '#':
    310 			p.parseComment()
    311 			break loop
    312 		case '$':
    313 			var variable Variable
    314 			variable = p.parseVariable()
    315 			value.appendVariable(variable)
    316 		case scanner.EOF:
    317 			break loop
    318 		case '(':
    319 			if endParen {
    320 				parens++
    321 			}
    322 			value.appendString("(")
    323 			p.accept('(')
    324 		default:
    325 			value.appendString(p.scanner.TokenText())
    326 			p.accept(p.tok)
    327 		}
    328 	}
    329 
    330 	if parens > 0 {
    331 		p.errorf("expected closing paren %s", value.Dump())
    332 	}
    333 	return value
    334 }
    335 
    336 func (p *parser) parseVariable() Variable {
    337 	pos := p.pos()
    338 	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 '$':
    346 		name = SimpleMakeString("__builtin_dollar", NoPos)
    347 	case scanner.EOF:
    348 		p.errorf("expected variable name, found %s",
    349 			scanner.TokenString(p.tok))
    350 	default:
    351 		name = p.parseExpression(variableNameEndRunes...)
    352 	}
    353 
    354 	return p.nameToVariable(name)
    355 }
    356 
    357 func (p *parser) parseBracketedVariable(start, end rune, pos Pos) Variable {
    358 	p.accept(start)
    359 	name := p.parseExpression(end)
    360 	p.accept(end)
    361 	return p.nameToVariable(name)
    362 }
    363 
    364 func (p *parser) nameToVariable(name *MakeString) Variable {
    365 	return Variable{
    366 		Name: name,
    367 	}
    368 }
    369 
    370 func (p *parser) parseRule(target *MakeString) {
    371 	prerequisites, newLine := p.parseRulePrerequisites(target)
    372 
    373 	recipe := ""
    374 	recipePos := p.pos()
    375 loop:
    376 	for {
    377 		if newLine {
    378 			if p.tok == '\t' {
    379 				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)
    395 			p.accept(p.tok)
    396 		case '\n':
    397 			newLine = true
    398 			recipe += "\n"
    399 			p.accept('\n')
    400 		case scanner.EOF:
    401 			break loop
    402 		default:
    403 			recipe += p.scanner.TokenText()
    404 			p.accept(p.tok)
    405 		}
    406 	}
    407 
    408 	if prerequisites != nil {
    409 		p.nodes = append(p.nodes, &Rule{
    410 			Target:        target,
    411 			Prerequisites: prerequisites,
    412 			Recipe:        recipe,
    413 			RecipePos:     recipePos,
    414 		})
    415 	}
    416 }
    417 
    418 func (p *parser) parseRulePrerequisites(target *MakeString) (*MakeString, bool) {
    419 	newLine := false
    420 
    421 	p.ignoreSpaces()
    422 
    423 	prerequisites := p.parseExpression('#', '\n', ';', ':', '=')
    424 
    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 {
    440 			more := p.parseExpression('#', '\n', ';')
    441 			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 
    453 func (p *parser) parseComment() {
    454 	pos := p.pos()
    455 	p.accept('#')
    456 	comment := ""
    457 loop:
    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 			}
    467 			p.accept(p.tok)
    468 		case '\n':
    469 			p.accept('\n')
    470 			break loop
    471 		case scanner.EOF:
    472 			break loop
    473 		default:
    474 			comment += p.scanner.TokenText()
    475 			p.accept(p.tok)
    476 		}
    477 	}
    478 
    479 	p.comments = append(p.comments, &Comment{
    480 		CommentPos: pos,
    481 		Comment:    comment,
    482 	})
    483 }
    484 
    485 func (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('=')
    490 	value := p.parseExpression()
    491 	value.TrimLeftSpaces()
    492 	if ident.EndsWith('+') && t == "=" {
    493 		ident.TrimRightOne()
    494 		t = "+="
    495 	}
    496 
    497 	ident.TrimRightSpaces()
    498 
    499 	p.nodes = append(p.nodes, &Assignment{
    500 		Name:   ident,
    501 		Value:  value,
    502 		Target: target,
    503 		Type:   t,
    504 	})
    505 }
    506 
    507 type androidMkModule struct {
    508 	assignments map[string]string
    509 }
    510 
    511 type androidMkFile struct {
    512 	assignments map[string]string
    513 	modules     []androidMkModule
    514 	includes    []string
    515 }
    516 
    517 var directives = [...]string{
    518 	"define",
    519 	"else",
    520 	"endef",
    521 	"endif",
    522 	"ifdef",
    523 	"ifeq",
    524 	"ifndef",
    525 	"ifneq",
    526 	"include",
    527 	"-include",
    528 }
    529 
    530 var 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 
    569 func init() {
    570 	sort.Strings(directives[:])
    571 	sort.Strings(functions[:])
    572 }
    573 
    574 func 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 
    585 func 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 
    596 func isWhitespace(ch rune) bool {
    597 	return ch == ' ' || ch == '\t' || ch == '\n'
    598 }
    599 
    600 func isValidVariableRune(ch rune) bool {
    601 	return ch != scanner.Ident && ch != ':' && ch != '=' && ch != '#'
    602 }
    603 
    604 var whitespaceRunes = []rune{' ', '\t', '\n'}
    605 var variableNameEndRunes = append([]rune{':', '=', '#', ')', '}'}, whitespaceRunes...)
    606 
    607 func (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 
    616 func (p *parser) ignoreWhitespace() {
    617 	for isWhitespace(p.tok) {
    618 		p.accept(p.tok)
    619 	}
    620 }
    621