Home | History | Annotate | Download | only in kati
      1 // Copyright 2015 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 
     15 package kati
     16 
     17 //go:generate go run testcase/gen_testcase_parse_benchmark.go
     18 //
     19 // $ go generate
     20 // $ go test -bench .
     21 
     22 import (
     23 	"bufio"
     24 	"bytes"
     25 	"crypto/sha1"
     26 	"errors"
     27 	"fmt"
     28 	"io"
     29 	"io/ioutil"
     30 	"strings"
     31 	"sync"
     32 	"time"
     33 
     34 	"github.com/golang/glog"
     35 )
     36 
     37 type makefile struct {
     38 	filename string
     39 	stmts    []ast
     40 }
     41 
     42 type ifState struct {
     43 	ast     *ifAST
     44 	inElse  bool
     45 	numNest int
     46 }
     47 
     48 type parser struct {
     49 	rd          *bufio.Reader
     50 	mk          makefile
     51 	lineno      int
     52 	elineno     int // lineno == elineno unless there is trailing '\'.
     53 	linenoFixed bool
     54 	done        bool
     55 	outStmts    *[]ast
     56 	inRecipe    bool
     57 	ifStack     []ifState
     58 
     59 	defineVar []byte
     60 	inDef     []byte
     61 
     62 	defOpt    string
     63 	numIfNest int
     64 	err       error
     65 }
     66 
     67 func newParser(rd io.Reader, filename string) *parser {
     68 	p := &parser{
     69 		rd: bufio.NewReader(rd),
     70 	}
     71 	p.mk.filename = filename
     72 	p.outStmts = &p.mk.stmts
     73 	return p
     74 }
     75 
     76 func (p *parser) srcpos() srcpos {
     77 	return srcpos{
     78 		filename: p.mk.filename,
     79 		lineno:   p.lineno,
     80 	}
     81 }
     82 
     83 func (p *parser) addStatement(stmt ast) {
     84 	*p.outStmts = append(*p.outStmts, stmt)
     85 	switch stmt.(type) {
     86 	case *maybeRuleAST:
     87 		p.inRecipe = true
     88 	case *assignAST, *includeAST, *exportAST:
     89 		p.inRecipe = false
     90 	}
     91 }
     92 
     93 func (p *parser) readLine() []byte {
     94 	if !p.linenoFixed {
     95 		p.lineno = p.elineno + 1
     96 	}
     97 	var line []byte
     98 	for !p.done {
     99 		buf, err := p.rd.ReadBytes('\n')
    100 		if !p.linenoFixed {
    101 			p.elineno++
    102 		}
    103 		if err == io.EOF {
    104 			p.done = true
    105 		} else if err != nil {
    106 			p.err = fmt.Errorf("readline %s: %v", p.srcpos(), err)
    107 			p.done = true
    108 		}
    109 		line = append(line, buf...)
    110 		buf = bytes.TrimRight(buf, "\r\n")
    111 		glog.V(4).Infof("buf:%q", buf)
    112 		backslash := false
    113 		for len(buf) > 0 && buf[len(buf)-1] == '\\' {
    114 			buf = buf[:len(buf)-1]
    115 			backslash = !backslash
    116 		}
    117 		if !backslash {
    118 			glog.V(4).Infof("no concat line:%q", buf)
    119 			break
    120 		}
    121 	}
    122 	line = bytes.TrimRight(line, "\r\n")
    123 	return line
    124 }
    125 
    126 func newAssignAST(p *parser, lhsBytes []byte, rhsBytes []byte, op string) (*assignAST, error) {
    127 	lhs, _, err := parseExpr(lhsBytes, nil, parseOp{alloc: true})
    128 	if err != nil {
    129 		return nil, err
    130 	}
    131 	rhs, _, err := parseExpr(rhsBytes, nil, parseOp{alloc: true})
    132 	if err != nil {
    133 		return nil, err
    134 	}
    135 	opt := ""
    136 	if p != nil {
    137 		opt = p.defOpt
    138 	}
    139 	return &assignAST{
    140 		lhs: lhs,
    141 		rhs: rhs,
    142 		op:  op,
    143 		opt: opt,
    144 	}, nil
    145 }
    146 
    147 func (p *parser) handleDirective(line []byte, directives map[string]directiveFunc) bool {
    148 	w, data := firstWord(line)
    149 	if d, ok := directives[string(w)]; ok {
    150 		d(p, data)
    151 		return true
    152 	}
    153 	return false
    154 }
    155 
    156 func (p *parser) handleRuleOrAssign(line []byte) {
    157 	rline := line
    158 	var semi []byte
    159 	if i := findLiteralChar(line, ';', 0, skipVar); i >= 0 {
    160 		// preserve after semicolon
    161 		semi = append(semi, line[i+1:]...)
    162 		rline = concatline(line[:i])
    163 	} else {
    164 		rline = concatline(line)
    165 	}
    166 	if p.handleAssign(line) {
    167 		return
    168 	}
    169 	// not assignment.
    170 	// ie. no '=' found or ':' found before '=' (except ':=')
    171 	p.parseMaybeRule(rline, semi)
    172 	return
    173 }
    174 
    175 func (p *parser) handleAssign(line []byte) bool {
    176 	aline, _ := removeComment(concatline(line))
    177 	aline = trimLeftSpaceBytes(aline)
    178 	if len(aline) == 0 {
    179 		return false
    180 	}
    181 	// fmt.Printf("assign: %q=>%q\n", line, aline)
    182 	i := findLiteralChar(aline, ':', '=', skipVar)
    183 	if i >= 0 {
    184 		if aline[i] == '=' {
    185 			p.parseAssign(aline, i)
    186 			return true
    187 		}
    188 		if aline[i] == ':' && i+1 < len(aline) && aline[i+1] == '=' {
    189 			p.parseAssign(aline, i+1)
    190 			return true
    191 		}
    192 	}
    193 	return false
    194 }
    195 
    196 func (p *parser) parseAssign(line []byte, sep int) {
    197 	lhs, op, rhs := line[:sep], line[sep:sep+1], line[sep+1:]
    198 	if sep > 0 {
    199 		switch line[sep-1] {
    200 		case ':', '+', '?':
    201 			lhs, op = line[:sep-1], line[sep-1:sep+1]
    202 		}
    203 	}
    204 	glog.V(1).Infof("parseAssign %s op:%q opt:%s", line, op, p.defOpt)
    205 	lhs = trimSpaceBytes(lhs)
    206 	rhs = trimLeftSpaceBytes(rhs)
    207 	aast, err := newAssignAST(p, lhs, rhs, string(op))
    208 	if err != nil {
    209 		p.err = err
    210 		return
    211 	}
    212 	aast.srcpos = p.srcpos()
    213 	p.addStatement(aast)
    214 }
    215 
    216 func (p *parser) parseMaybeRule(line, semi []byte) {
    217 	if len(line) == 0 {
    218 		p.err = p.srcpos().errorf("*** missing rule before commands.")
    219 		return
    220 	}
    221 	if line[0] == '\t' {
    222 		p.err = p.srcpos().errorf("*** commands commence before first target.")
    223 		return
    224 	}
    225 	var assign *assignAST
    226 	ci := findLiteralChar(line, ':', 0, skipVar)
    227 	if ci >= 0 {
    228 		eqi := findLiteralChar(line[ci+1:], '=', 0, skipVar)
    229 		if eqi == 0 {
    230 			panic(fmt.Sprintf("unexpected eq after colon: %q", line))
    231 		}
    232 		if eqi > 0 {
    233 			var lhsbytes []byte
    234 			op := "="
    235 			switch line[ci+1+eqi-1] {
    236 			case ':', '+', '?':
    237 				lhsbytes = append(lhsbytes, line[ci+1:ci+1+eqi-1]...)
    238 				op = string(line[ci+1+eqi-1 : ci+1+eqi+1])
    239 			default:
    240 				lhsbytes = append(lhsbytes, line[ci+1:ci+1+eqi]...)
    241 			}
    242 
    243 			lhsbytes = trimSpaceBytes(lhsbytes)
    244 			lhs, _, err := parseExpr(lhsbytes, nil, parseOp{})
    245 			if err != nil {
    246 				p.err = p.srcpos().error(err)
    247 				return
    248 			}
    249 			var rhsbytes []byte
    250 			rhsbytes = append(rhsbytes, line[ci+1+eqi+1:]...)
    251 			if semi != nil {
    252 				rhsbytes = append(rhsbytes, ';')
    253 				rhsbytes = append(rhsbytes, concatline(semi)...)
    254 			}
    255 			rhsbytes = trimLeftSpaceBytes(rhsbytes)
    256 			semi = nil
    257 			rhs, _, err := parseExpr(rhsbytes, nil, parseOp{})
    258 			if err != nil {
    259 				p.err = p.srcpos().error(err)
    260 				return
    261 			}
    262 
    263 			// TODO(ukai): support override, export in target specific var.
    264 			assign = &assignAST{
    265 				lhs: lhs,
    266 				rhs: rhs,
    267 				op:  op,
    268 			}
    269 			assign.srcpos = p.srcpos()
    270 			line = line[:ci+1]
    271 		}
    272 	}
    273 	expr, _, err := parseExpr(line, nil, parseOp{})
    274 	if err != nil {
    275 		p.err = p.srcpos().error(err)
    276 		return
    277 	}
    278 	// TODO(ukai): remove ast, and eval here.
    279 	rast := &maybeRuleAST{
    280 		isRule: ci >= 0,
    281 		expr:   expr,
    282 		assign: assign,
    283 		semi:   semi,
    284 	}
    285 	rast.srcpos = p.srcpos()
    286 	glog.V(1).Infof("stmt: %#v", rast)
    287 	p.addStatement(rast)
    288 }
    289 
    290 func (p *parser) parseInclude(op string, line []byte) {
    291 	// TODO(ukai): parse expr here
    292 	iast := &includeAST{
    293 		expr: string(line),
    294 		op:   op,
    295 	}
    296 	iast.srcpos = p.srcpos()
    297 	p.addStatement(iast)
    298 }
    299 
    300 func (p *parser) parseIfdef(op string, data []byte) {
    301 	lhs, _, err := parseExpr(data, nil, parseOp{alloc: true})
    302 	if err != nil {
    303 		p.err = p.srcpos().error(err)
    304 		return
    305 	}
    306 	iast := &ifAST{
    307 		op:  op,
    308 		lhs: lhs,
    309 	}
    310 	iast.srcpos = p.srcpos()
    311 	p.addStatement(iast)
    312 	p.ifStack = append(p.ifStack, ifState{ast: iast, numNest: p.numIfNest})
    313 	p.outStmts = &iast.trueStmts
    314 }
    315 
    316 func (p *parser) parseTwoQuotes(s []byte) (string, string, []byte, bool) {
    317 	var args []string
    318 	for i := 0; i < 2; i++ {
    319 		s = trimSpaceBytes(s)
    320 		if len(s) == 0 {
    321 			return "", "", nil, false
    322 		}
    323 		quote := s[0]
    324 		if quote != '\'' && quote != '"' {
    325 			return "", "", nil, false
    326 		}
    327 		end := bytes.IndexByte(s[1:], quote) + 1
    328 		if end < 0 {
    329 			return "", "", nil, false
    330 		}
    331 		args = append(args, string(s[1:end]))
    332 		s = s[end+1:]
    333 	}
    334 	return args[0], args[1], s, true
    335 }
    336 
    337 // parse
    338 //  "(lhs, rhs)"
    339 //  "lhs, rhs"
    340 func (p *parser) parseEq(s []byte) (string, string, []byte, bool) {
    341 	if len(s) == 0 {
    342 		return "", "", nil, false
    343 	}
    344 	if s[0] == '(' {
    345 		in := s[1:]
    346 		glog.V(1).Infof("parseEq ( %q )", in)
    347 		term := []byte{','}
    348 		v, n, err := parseExpr(in, term, parseOp{matchParen: true})
    349 		if err != nil {
    350 			glog.V(1).Infof("parse eq: %q: %v", in, err)
    351 			return "", "", nil, false
    352 		}
    353 		lhs := v.String()
    354 		n++
    355 		n += skipSpaces(in[n:], nil)
    356 		term = []byte{')'}
    357 		in = in[n:]
    358 		v, n, err = parseExpr(in, term, parseOp{matchParen: true})
    359 		if err != nil {
    360 			glog.V(1).Infof("parse eq 2nd: %q: %v", in, err)
    361 			return "", "", nil, false
    362 		}
    363 		rhs := v.String()
    364 		in = in[n+1:]
    365 		in = trimSpaceBytes(in)
    366 		return lhs, rhs, in, true
    367 	}
    368 	return p.parseTwoQuotes(s)
    369 }
    370 
    371 func (p *parser) parseIfeq(op string, data []byte) {
    372 	lhsBytes, rhsBytes, extra, ok := p.parseEq(data)
    373 	if !ok {
    374 		p.err = p.srcpos().errorf(`*** invalid syntax in conditional.`)
    375 		return
    376 	}
    377 	if len(extra) > 0 {
    378 		glog.V(1).Infof("extra %q", extra)
    379 		warnNoPrefix(p.srcpos(), `extraneous text after %q directive`, op)
    380 	}
    381 
    382 	lhs, _, err := parseExpr([]byte(lhsBytes), nil, parseOp{matchParen: true})
    383 	if err != nil {
    384 		p.err = p.srcpos().error(err)
    385 		return
    386 	}
    387 	rhs, _, err := parseExpr([]byte(rhsBytes), nil, parseOp{matchParen: true})
    388 	if err != nil {
    389 		p.err = p.srcpos().error(err)
    390 		return
    391 	}
    392 
    393 	iast := &ifAST{
    394 		op:  op,
    395 		lhs: lhs,
    396 		rhs: rhs,
    397 	}
    398 	iast.srcpos = p.srcpos()
    399 	p.addStatement(iast)
    400 	p.ifStack = append(p.ifStack, ifState{ast: iast, numNest: p.numIfNest})
    401 	p.outStmts = &iast.trueStmts
    402 }
    403 
    404 func (p *parser) checkIfStack(curKeyword string) error {
    405 	if len(p.ifStack) == 0 {
    406 		return p.srcpos().errorf(`*** extraneous %q.`, curKeyword)
    407 	}
    408 	return nil
    409 }
    410 
    411 func (p *parser) parseElse(data []byte) {
    412 	err := p.checkIfStack("else")
    413 	if err != nil {
    414 		p.err = err
    415 		return
    416 	}
    417 	state := &p.ifStack[len(p.ifStack)-1]
    418 	if state.inElse {
    419 		p.err = p.srcpos().errorf(`*** only one "else" per conditional.`)
    420 		return
    421 	}
    422 	state.inElse = true
    423 	p.outStmts = &state.ast.falseStmts
    424 
    425 	nextIf := data
    426 	if len(nextIf) == 0 {
    427 		return
    428 	}
    429 	var ifDirectives = map[string]directiveFunc{
    430 		"ifdef":  ifdefDirective,
    431 		"ifndef": ifndefDirective,
    432 		"ifeq":   ifeqDirective,
    433 		"ifneq":  ifneqDirective,
    434 	}
    435 	p.numIfNest = state.numNest + 1
    436 	if p.handleDirective(nextIf, ifDirectives) {
    437 		p.numIfNest = 0
    438 		return
    439 	}
    440 	p.numIfNest = 0
    441 	warnNoPrefix(p.srcpos(), "extraneous text after `else' directive")
    442 	return
    443 }
    444 
    445 func (p *parser) parseEndif(data []byte) {
    446 	err := p.checkIfStack("endif")
    447 	if err != nil {
    448 		p.err = err
    449 		return
    450 	}
    451 	state := p.ifStack[len(p.ifStack)-1]
    452 	for t := 0; t <= state.numNest; t++ {
    453 		p.ifStack = p.ifStack[0 : len(p.ifStack)-1]
    454 		if len(p.ifStack) == 0 {
    455 			p.outStmts = &p.mk.stmts
    456 		} else {
    457 			state := p.ifStack[len(p.ifStack)-1]
    458 			if state.inElse {
    459 				p.outStmts = &state.ast.falseStmts
    460 			} else {
    461 				p.outStmts = &state.ast.trueStmts
    462 			}
    463 		}
    464 	}
    465 	if len(trimSpaceBytes(data)) > 0 {
    466 		warnNoPrefix(p.srcpos(), "extraneous text after `endif' directive")
    467 	}
    468 	return
    469 }
    470 
    471 func (p *parser) parseDefine(data []byte) {
    472 	p.defineVar = nil
    473 	p.inDef = nil
    474 	p.defineVar = append(p.defineVar, trimSpaceBytes(data)...)
    475 	return
    476 }
    477 
    478 func (p *parser) parseVpath(data []byte) {
    479 	vline, _ := removeComment(concatline(data))
    480 	vline = trimLeftSpaceBytes(vline)
    481 	v, _, err := parseExpr(vline, nil, parseOp{})
    482 	if err != nil {
    483 		p.err = p.srcpos().errorf("parse error %q: %v", string(vline), err)
    484 		return
    485 	}
    486 	vast := &vpathAST{
    487 		expr: v,
    488 	}
    489 	vast.srcpos = p.srcpos()
    490 	p.addStatement(vast)
    491 }
    492 
    493 type directiveFunc func(*parser, []byte)
    494 
    495 var makeDirectives map[string]directiveFunc
    496 
    497 func init() {
    498 	makeDirectives = map[string]directiveFunc{
    499 		"include":  includeDirective,
    500 		"-include": sincludeDirective,
    501 		"sinclude": sincludeDirective,
    502 		"ifdef":    ifdefDirective,
    503 		"ifndef":   ifndefDirective,
    504 		"ifeq":     ifeqDirective,
    505 		"ifneq":    ifneqDirective,
    506 		"else":     elseDirective,
    507 		"endif":    endifDirective,
    508 		"define":   defineDirective,
    509 		"override": overrideDirective,
    510 		"export":   exportDirective,
    511 		"unexport": unexportDirective,
    512 		"vpath":    vpathDirective,
    513 	}
    514 }
    515 
    516 func includeDirective(p *parser, data []byte) {
    517 	p.parseInclude("include", data)
    518 }
    519 
    520 func sincludeDirective(p *parser, data []byte) {
    521 	p.parseInclude("-include", data)
    522 }
    523 
    524 func ifdefDirective(p *parser, data []byte) {
    525 	p.parseIfdef("ifdef", data)
    526 }
    527 
    528 func ifndefDirective(p *parser, data []byte) {
    529 	p.parseIfdef("ifndef", data)
    530 }
    531 
    532 func ifeqDirective(p *parser, data []byte) {
    533 	p.parseIfeq("ifeq", data)
    534 }
    535 
    536 func ifneqDirective(p *parser, data []byte) {
    537 	p.parseIfeq("ifneq", data)
    538 }
    539 
    540 func elseDirective(p *parser, data []byte) {
    541 	p.parseElse(data)
    542 }
    543 
    544 func endifDirective(p *parser, data []byte) {
    545 	p.parseEndif(data)
    546 }
    547 
    548 func defineDirective(p *parser, data []byte) {
    549 	p.parseDefine(data)
    550 }
    551 
    552 func overrideDirective(p *parser, data []byte) {
    553 	p.defOpt = "override"
    554 	defineDirective := map[string]directiveFunc{
    555 		"define": defineDirective,
    556 	}
    557 	glog.V(1).Infof("override define? %q", data)
    558 	if p.handleDirective(data, defineDirective) {
    559 		return
    560 	}
    561 	// e.g. overrider foo := bar
    562 	// line will be "foo := bar".
    563 	if p.handleAssign(data) {
    564 		return
    565 	}
    566 	p.defOpt = ""
    567 	var line []byte
    568 	line = append(line, []byte("override ")...)
    569 	line = append(line, data...)
    570 	p.handleRuleOrAssign(line)
    571 	// TODO(ukai): evaluate now to detect invalid "override" directive here?
    572 }
    573 
    574 func handleExport(p *parser, data []byte, export bool) (hasEqual bool) {
    575 	i := bytes.IndexByte(data, '=')
    576 	if i > 0 {
    577 		hasEqual = true
    578 		switch data[i-1] {
    579 		case ':', '+', '?':
    580 			i--
    581 		}
    582 		data = data[:i]
    583 	}
    584 	east := &exportAST{
    585 		expr:     data,
    586 		hasEqual: hasEqual,
    587 		export:   export,
    588 	}
    589 	east.srcpos = p.srcpos()
    590 	glog.V(1).Infof("export %v", east)
    591 	p.addStatement(east)
    592 	return hasEqual
    593 }
    594 
    595 func exportDirective(p *parser, data []byte) {
    596 	p.defOpt = "export"
    597 	defineDirective := map[string]directiveFunc{
    598 		"define": defineDirective,
    599 	}
    600 	glog.V(1).Infof("export define? %q", data)
    601 	if p.handleDirective(data, defineDirective) {
    602 		return
    603 	}
    604 
    605 	if !handleExport(p, data, true) {
    606 		return
    607 	}
    608 
    609 	// e.g. export foo := bar
    610 	// line will be "foo := bar".
    611 	p.handleAssign(data)
    612 }
    613 
    614 func unexportDirective(p *parser, data []byte) {
    615 	handleExport(p, data, false)
    616 	return
    617 }
    618 
    619 func vpathDirective(p *parser, data []byte) {
    620 	p.parseVpath(data)
    621 }
    622 
    623 func (p *parser) parse() (mk makefile, err error) {
    624 	for !p.done {
    625 		line := p.readLine()
    626 		if glog.V(1) {
    627 			glog.Infof("%s: %q", p.srcpos(), line)
    628 		}
    629 		if p.defineVar != nil {
    630 			p.processDefine(line)
    631 			if p.err != nil {
    632 				return makefile{}, p.err
    633 			}
    634 			continue
    635 		}
    636 		p.defOpt = ""
    637 		if p.inRecipe {
    638 			if len(line) > 0 && line[0] == '\t' {
    639 				cast := &commandAST{cmd: string(line[1:])}
    640 				cast.srcpos = p.srcpos()
    641 				p.addStatement(cast)
    642 				continue
    643 			}
    644 		}
    645 		p.parseLine(line)
    646 		if p.err != nil {
    647 			return makefile{}, p.err
    648 		}
    649 	}
    650 	return p.mk, p.err
    651 }
    652 
    653 func (p *parser) parseLine(line []byte) {
    654 	cline := concatline(line)
    655 	if len(cline) == 0 {
    656 		return
    657 	}
    658 	if glog.V(1) {
    659 		glog.Infof("concatline:%q", cline)
    660 	}
    661 	var dline []byte
    662 	cline, _ = removeComment(cline)
    663 	dline = append(dline, cline...)
    664 	dline = trimSpaceBytes(dline)
    665 	if len(dline) == 0 {
    666 		return
    667 	}
    668 	if glog.V(1) {
    669 		glog.Infof("directive?: %q", dline)
    670 	}
    671 	if p.handleDirective(dline, makeDirectives) {
    672 		return
    673 	}
    674 	if glog.V(1) {
    675 		glog.Infof("rule or assign?: %q", line)
    676 	}
    677 	p.handleRuleOrAssign(line)
    678 }
    679 
    680 func (p *parser) processDefine(line []byte) {
    681 	line = append(line, '\n')
    682 	line = concatline(line)
    683 	if line[len(line)-1] != '\n' {
    684 		line = append(line, '\n')
    685 	}
    686 	if glog.V(1) {
    687 		glog.Infof("concatline:%q", line)
    688 	}
    689 	if !p.isEndef(line) {
    690 		p.inDef = append(p.inDef, line...)
    691 		if p.inDef == nil {
    692 			p.inDef = []byte{}
    693 		}
    694 		return
    695 	}
    696 	if p.inDef[len(p.inDef)-1] == '\n' {
    697 		p.inDef = p.inDef[:len(p.inDef)-1]
    698 	}
    699 	glog.V(1).Infof("multilineAssign %q %q", p.defineVar, p.inDef)
    700 	aast, err := newAssignAST(p, p.defineVar, p.inDef, "=")
    701 	if err != nil {
    702 		p.err = p.srcpos().errorf("assign error %q=%q: %v", p.defineVar, p.inDef, err)
    703 		return
    704 	}
    705 	aast.srcpos = p.srcpos()
    706 	aast.srcpos.lineno -= bytes.Count(p.inDef, []byte{'\n'})
    707 	p.addStatement(aast)
    708 	p.defineVar = nil
    709 	p.inDef = nil
    710 	return
    711 }
    712 
    713 func (p *parser) isEndef(line []byte) bool {
    714 	if bytes.Equal(line, []byte("endef")) {
    715 		return true
    716 	}
    717 	w, data := firstWord(line)
    718 	if bytes.Equal(w, []byte("endef")) {
    719 		data, _ = removeComment(data)
    720 		data = trimLeftSpaceBytes(data)
    721 		if len(data) > 0 {
    722 			warnNoPrefix(p.srcpos(), `extraneous text after "endef" directive`)
    723 		}
    724 		return true
    725 	}
    726 	return false
    727 }
    728 
    729 func defaultMakefile() (string, error) {
    730 	candidates := []string{"GNUmakefile", "makefile", "Makefile"}
    731 	for _, filename := range candidates {
    732 		if exists(filename) {
    733 			return filename, nil
    734 		}
    735 	}
    736 	return "", errors.New("no targets specified and no makefile found")
    737 }
    738 
    739 func parseMakefileReader(rd io.Reader, loc srcpos) (makefile, error) {
    740 	parser := newParser(rd, loc.filename)
    741 	parser.lineno = loc.lineno
    742 	parser.elineno = loc.lineno
    743 	parser.linenoFixed = true
    744 	return parser.parse()
    745 }
    746 
    747 func parseMakefileString(s string, loc srcpos) (makefile, error) {
    748 	return parseMakefileReader(strings.NewReader(s), loc)
    749 }
    750 
    751 func parseMakefileBytes(s []byte, loc srcpos) (makefile, error) {
    752 	return parseMakefileReader(bytes.NewReader(s), loc)
    753 }
    754 
    755 type mkCacheEntry struct {
    756 	mk   makefile
    757 	hash [sha1.Size]byte
    758 	err  error
    759 	ts   int64
    760 }
    761 
    762 type makefileCacheT struct {
    763 	mu sync.Mutex
    764 	mk map[string]mkCacheEntry
    765 }
    766 
    767 var makefileCache = &makefileCacheT{
    768 	mk: make(map[string]mkCacheEntry),
    769 }
    770 
    771 func (mc *makefileCacheT) lookup(filename string) (makefile, [sha1.Size]byte, bool, error) {
    772 	var hash [sha1.Size]byte
    773 	mc.mu.Lock()
    774 	c, present := mc.mk[filename]
    775 	mc.mu.Unlock()
    776 	if !present {
    777 		return makefile{}, hash, false, nil
    778 	}
    779 	ts := getTimestamp(filename)
    780 	if ts < 0 || ts >= c.ts {
    781 		return makefile{}, hash, false, nil
    782 	}
    783 	return c.mk, c.hash, true, c.err
    784 }
    785 
    786 func (mc *makefileCacheT) parse(filename string) (makefile, [sha1.Size]byte, error) {
    787 	glog.Infof("parse Makefile %q", filename)
    788 	mk, hash, ok, err := makefileCache.lookup(filename)
    789 	if ok {
    790 		if glog.V(1) {
    791 			glog.Infof("makefile cache hit for %q", filename)
    792 		}
    793 		return mk, hash, err
    794 	}
    795 	if glog.V(1) {
    796 		glog.Infof("reading makefile %q", filename)
    797 	}
    798 	c, err := ioutil.ReadFile(filename)
    799 	if err != nil {
    800 		return makefile{}, hash, err
    801 	}
    802 	hash = sha1.Sum(c)
    803 	mk, err = parseMakefile(c, filename)
    804 	if err != nil {
    805 		return makefile{}, hash, err
    806 	}
    807 	makefileCache.mu.Lock()
    808 	makefileCache.mk[filename] = mkCacheEntry{
    809 		mk:   mk,
    810 		hash: hash,
    811 		err:  err,
    812 		ts:   time.Now().Unix(),
    813 	}
    814 	makefileCache.mu.Unlock()
    815 	return mk, hash, err
    816 }
    817 
    818 func parseMakefile(s []byte, filename string) (makefile, error) {
    819 	parser := newParser(bytes.NewReader(s), filename)
    820 	return parser.parse()
    821 }
    822