mk/parse.go

375 lines
8.8 KiB
Go
Raw Normal View History

2013-02-26 22:41:25 -08:00
// This is a mkfile parser. It executes assignments and includes as it goes, and
// collects a set of rules, which are returned as a ruleSet object.
2013-02-25 23:52:08 -08:00
package main
import (
2013-02-26 11:33:07 -08:00
"fmt"
2013-03-10 00:34:42 -08:00
"io/ioutil"
"os"
"path/filepath"
2013-03-03 17:51:00 -08:00
"regexp"
"strings"
2013-02-25 23:52:08 -08:00
)
2013-02-26 11:33:07 -08:00
type parser struct {
l *lexer // underlying lexer
name string // name of the file being parsed
path string // full path of the file being parsed
2013-02-26 11:33:07 -08:00
tokenbuf []token // tokens consumed on the current statement
rules *ruleSet // current ruleSet
}
2013-02-25 23:52:08 -08:00
2013-02-26 22:41:25 -08:00
// Pretty errors.
2013-02-26 11:33:07 -08:00
func (p *parser) parseError(context string, expected string, found token) {
2013-03-10 00:34:42 -08:00
mkPrintError(fmt.Sprintf("%s:%d: syntax error: ", p.name, found.line))
2013-03-09 20:54:13 -08:00
mkPrintError(fmt.Sprintf("while %s, expected %s but found '%s'.\n",
context, expected, found.String()))
2013-03-10 00:34:42 -08:00
mkError("")
2013-02-26 11:33:07 -08:00
}
2013-02-25 23:52:08 -08:00
2013-02-26 22:41:25 -08:00
// More basic errors.
2013-02-26 11:33:07 -08:00
func (p *parser) basicErrorAtToken(what string, found token) {
p.basicErrorAtLine(what, found.line)
}
2013-02-25 23:52:08 -08:00
2013-02-26 11:33:07 -08:00
func (p *parser) basicErrorAtLine(what string, line int) {
2013-03-09 20:54:13 -08:00
mkError(fmt.Sprintf("%s:%d: syntax error: %s\n", p.name, line, what))
2013-02-26 11:33:07 -08:00
}
2013-02-25 23:52:08 -08:00
2013-02-26 22:41:25 -08:00
// Accept a token for use in the current statement being parsed.
2013-02-26 11:33:07 -08:00
func (p *parser) push(t token) {
p.tokenbuf = append(p.tokenbuf, t)
2013-02-25 23:52:08 -08:00
}
2013-02-26 22:41:25 -08:00
// Clear all the accepted tokens. Called when a statement is finished.
2013-02-26 11:33:07 -08:00
func (p *parser) clear() {
p.tokenbuf = p.tokenbuf[:0]
}
2013-02-25 23:52:08 -08:00
// A parser state function takes a parser and the next token and returns a new
// state function, or nil if there was a parse error.
2013-02-26 11:33:07 -08:00
type parserStateFun func(*parser, token) parserStateFun
2013-02-25 23:52:08 -08:00
// Parse a mkfile, returning a new ruleSet.
func parse(input string, name string, path string) *ruleSet {
rules := &ruleSet{make(map[string][]string),
2013-03-03 17:51:00 -08:00
make([]rule, 0),
make(map[string][]int)}
parseInto(input, name, rules, path)
2013-02-26 11:33:07 -08:00
return rules
2013-02-25 23:52:08 -08:00
}
// Parse a mkfile inserting rules and variables into a given ruleSet.
func parseInto(input string, name string, rules *ruleSet, path string) {
2013-02-26 11:33:07 -08:00
l, tokens := lex(input)
p := &parser{l, name, path, []token{}, rules}
2014-08-05 18:28:00 -07:00
oldmkfiledir := p.rules.vars["mkfiledir"]
p.rules.vars["mkfiledir"] = []string{filepath.Dir(path)}
2013-02-26 11:33:07 -08:00
state := parseTopLevel
for t := range tokens {
if t.typ == tokenError {
2013-03-10 00:34:42 -08:00
p.basicErrorAtLine(l.errmsg, t.line)
2013-02-26 11:33:07 -08:00
break
}
state = state(p, t)
}
// insert a dummy newline to allow parsing of any assignments or recipeless
// rules to finish.
2013-03-03 18:57:14 -08:00
state = state(p, token{tokenNewline, "\n", l.line, l.col})
2013-02-26 11:33:07 -08:00
2014-08-05 18:28:00 -07:00
p.rules.vars["mkfiledir"] = oldmkfiledir
2013-02-26 22:41:25 -08:00
// TODO: Error when state != parseTopLevel
2013-02-26 11:33:07 -08:00
}
2013-02-25 23:52:08 -08:00
2013-02-26 22:41:25 -08:00
// We are at the top level of a mkfile, expecting rules, assignments, or
// includes.
2013-02-26 11:33:07 -08:00
func parseTopLevel(p *parser, t token) parserStateFun {
switch t.typ {
case tokenNewline:
return parseTopLevel
case tokenPipeInclude:
return parsePipeInclude
case tokenRedirInclude:
return parseRedirInclude
2013-02-26 22:41:25 -08:00
case tokenWord:
2013-02-26 11:33:07 -08:00
return parseAssignmentOrTarget(p, t)
default:
p.parseError("parsing mkfile",
"a rule, include, or assignment", t)
}
return parseTopLevel
}
2013-02-25 23:52:08 -08:00
2013-02-26 22:41:25 -08:00
// Consumed a '<|'
2013-02-26 11:33:07 -08:00
func parsePipeInclude(p *parser, t token) parserStateFun {
switch t.typ {
case tokenNewline:
if len(p.tokenbuf) == 0 {
p.basicErrorAtToken("empty pipe include", t)
}
2013-08-13 13:03:21 -07:00
args := make([]string, len(p.tokenbuf))
for i := 0; i < len(p.tokenbuf); i++ {
args[i] = p.tokenbuf[i].val
2013-02-26 11:33:07 -08:00
}
output, success := subprocess("sh", args, "", true)
2013-03-03 17:51:00 -08:00
if !success {
p.basicErrorAtToken("subprocess include failed", t)
}
2013-03-03 17:50:00 -08:00
parseInto(output, fmt.Sprintf("%s:sh", p.name), p.rules, p.path)
2013-02-26 11:33:07 -08:00
p.clear()
return parseTopLevel
// Almost anything goes. Let the shell sort it out.
case tokenPipeInclude:
fallthrough
case tokenRedirInclude:
fallthrough
case tokenColon:
fallthrough
case tokenAssign:
fallthrough
2013-02-26 22:41:25 -08:00
case tokenWord:
2013-02-26 11:33:07 -08:00
p.tokenbuf = append(p.tokenbuf, t)
default:
2013-03-10 00:34:42 -08:00
p.parseError("parsing piped include", "a shell command", t)
2013-02-26 11:33:07 -08:00
}
return parsePipeInclude
2013-02-25 23:52:08 -08:00
}
2013-02-26 22:41:25 -08:00
// Consumed a '<'
2013-02-26 11:33:07 -08:00
func parseRedirInclude(p *parser, t token) parserStateFun {
switch t.typ {
case tokenNewline:
2013-03-10 00:34:42 -08:00
filename := ""
for i := range p.tokenbuf {
filename += p.tokenbuf[i].val
}
file, err := os.Open(filename)
if err != nil {
p.basicErrorAtToken(fmt.Sprintf("cannot open %s", filename), p.tokenbuf[0])
}
input, _ := ioutil.ReadAll(file)
path, err := filepath.Abs(filename)
if err != nil {
mkError("unable to find mkfile's absolute path")
}
parseInto(string(input), filename, p.rules, path)
2013-03-10 00:34:42 -08:00
p.clear()
return parseTopLevel
2013-02-25 23:52:08 -08:00
2013-02-26 22:41:25 -08:00
case tokenWord:
2013-03-09 20:54:13 -08:00
p.tokenbuf = append(p.tokenbuf, t)
2013-02-25 23:52:08 -08:00
2013-02-26 11:33:07 -08:00
default:
2013-03-10 00:34:42 -08:00
p.parseError("parsing include", "a file name", t)
2013-02-26 11:33:07 -08:00
}
return parseRedirInclude
2013-02-25 23:52:08 -08:00
}
2013-02-26 11:33:07 -08:00
// Encountered a bare string at the beginning of the line.
func parseAssignmentOrTarget(p *parser, t token) parserStateFun {
p.push(t)
return parseEqualsOrTarget
}
2013-02-25 23:52:08 -08:00
2013-02-26 22:41:25 -08:00
// Consumed one bare string ot the beginning of the line.
2013-02-26 11:33:07 -08:00
func parseEqualsOrTarget(p *parser, t token) parserStateFun {
switch t.typ {
case tokenAssign:
return parseAssignment
2013-02-26 22:41:25 -08:00
case tokenWord:
2013-02-26 11:33:07 -08:00
p.push(t)
return parseTargets
case tokenColon:
p.push(t)
return parseAttributesOrPrereqs
default:
2013-03-09 20:54:13 -08:00
p.parseError("reading a target or assignment",
2013-02-26 11:33:07 -08:00
"'=', ':', or another target", t)
}
return parseTopLevel // unreachable
2013-02-25 23:52:08 -08:00
}
2013-02-26 11:33:07 -08:00
// Consumed 'foo='. Everything else is a value being assigned to foo.
func parseAssignment(p *parser, t token) parserStateFun {
switch t.typ {
case tokenNewline:
2013-02-26 22:41:25 -08:00
err := p.rules.executeAssignment(p.tokenbuf)
if err != nil {
p.basicErrorAtToken(err.what, err.where)
}
2013-02-26 11:33:07 -08:00
p.clear()
return parseTopLevel
2013-02-25 23:52:08 -08:00
2013-02-26 11:33:07 -08:00
default:
p.push(t)
}
return parseAssignment
2013-02-25 23:52:08 -08:00
}
2013-02-26 22:41:25 -08:00
// Everything up to ':' must be a target.
2013-02-26 11:33:07 -08:00
func parseTargets(p *parser, t token) parserStateFun {
switch t.typ {
2013-02-26 22:41:25 -08:00
case tokenWord:
2013-02-26 11:33:07 -08:00
p.push(t)
case tokenColon:
p.push(t)
return parseAttributesOrPrereqs
default:
p.parseError("reading a rule's targets",
"filename or pattern", t)
}
return parseTargets
}
2013-02-25 23:52:08 -08:00
2014-08-04 10:52:31 -07:00
// Consume one or more strings followed by a first ':'.
2013-02-26 11:33:07 -08:00
func parseAttributesOrPrereqs(p *parser, t token) parserStateFun {
switch t.typ {
case tokenNewline:
return parseRecipe
case tokenColon:
p.push(t)
return parsePrereqs
2013-02-26 22:41:25 -08:00
case tokenWord:
2013-02-26 11:33:07 -08:00
p.push(t)
default:
p.parseError("reading a rule's attributes or prerequisites",
"an attribute, pattern, or filename", t)
}
return parseAttributesOrPrereqs
}
2013-02-25 23:52:08 -08:00
2013-02-26 22:41:25 -08:00
// Targets and attributes and the second ':' have been consumed.
2013-02-26 11:33:07 -08:00
func parsePrereqs(p *parser, t token) parserStateFun {
switch t.typ {
case tokenNewline:
return parseRecipe
2013-02-26 22:41:25 -08:00
case tokenWord:
2013-02-26 11:33:07 -08:00
p.push(t)
default:
p.parseError("reading a rule's prerequisites",
"filename or pattern", t)
}
return parsePrereqs
}
2013-02-26 22:41:25 -08:00
// An entire rule has been consumed.
2013-02-26 11:33:07 -08:00
func parseRecipe(p *parser, t token) parserStateFun {
// Assemble the rule!
r := rule{}
// find one or two colons
i := 0
for ; i < len(p.tokenbuf) && p.tokenbuf[i].typ != tokenColon; i++ {
}
j := i + 1
for ; j < len(p.tokenbuf) && p.tokenbuf[j].typ != tokenColon; j++ {
}
// rule has attributes
if j < len(p.tokenbuf) {
2013-03-02 10:57:40 -08:00
attribs := make([]string, 0)
2013-02-26 11:33:07 -08:00
for k := i + 1; k < j; k++ {
2013-03-03 17:51:00 -08:00
exparts := expand(p.tokenbuf[k].val, p.rules.vars, true)
attribs = append(attribs, exparts...)
2013-02-26 11:33:07 -08:00
}
err := r.parseAttribs(attribs)
if err != nil {
2013-02-26 22:41:25 -08:00
msg := fmt.Sprintf("while reading a rule's attributes expected an attribute but found \"%c\".", err.found)
2013-02-26 11:33:07 -08:00
p.basicErrorAtToken(msg, p.tokenbuf[i+1])
}
2013-03-03 17:51:00 -08:00
if r.attributes.regex {
r.ismeta = true
}
2013-02-26 11:33:07 -08:00
} else {
j = i
}
// targets
2013-03-02 10:57:40 -08:00
r.targets = make([]pattern, 0)
for k := 0; k < i; k++ {
2013-03-03 17:51:00 -08:00
exparts := expand(p.tokenbuf[k].val, p.rules.vars, true)
for i := range exparts {
targetstr := exparts[i]
r.targets = append(r.targets, pattern{spat: targetstr})
if r.attributes.regex {
2014-02-01 19:13:27 -08:00
rpat, err := regexp.Compile("^" + targetstr + "$")
2013-03-03 17:51:00 -08:00
if err != nil {
msg := fmt.Sprintf("invalid regular expression: %q", err)
p.basicErrorAtToken(msg, p.tokenbuf[k])
}
r.targets[len(r.targets)-1].rpat = rpat
} else {
idx := strings.IndexRune(targetstr, '%')
if idx >= 0 {
var left, right string
if idx > 0 {
left = regexp.QuoteMeta(targetstr[:idx])
}
if idx < len(targetstr)-1 {
right = regexp.QuoteMeta(targetstr[idx+1:])
}
patstr := fmt.Sprintf("^%s(.*)%s$", left, right)
rpat, err := regexp.Compile(patstr)
if err != nil {
msg := fmt.Sprintf("error compiling suffix rule. This is a bug. Error: %s", err)
2013-03-03 17:51:00 -08:00
p.basicErrorAtToken(msg, p.tokenbuf[k])
}
r.targets[len(r.targets)-1].rpat = rpat
r.targets[len(r.targets)-1].issuffix = true
r.ismeta = true
}
}
}
}
2013-02-26 11:33:07 -08:00
// prereqs
2013-03-02 10:57:40 -08:00
r.prereqs = make([]string, 0)
2013-02-26 11:33:07 -08:00
for k := j + 1; k < len(p.tokenbuf); k++ {
2013-03-03 17:51:00 -08:00
exparts := expand(p.tokenbuf[k].val, p.rules.vars, true)
r.prereqs = append(r.prereqs, exparts...)
2013-02-26 11:33:07 -08:00
}
if t.typ == tokenRecipe {
r.recipe = expandRecipeSigils(stripIndentation(t.val, t.col), p.rules.vars)
2013-02-26 11:33:07 -08:00
}
p.rules.add(r)
2013-02-26 11:33:07 -08:00
p.clear()
// the current token doesn't belong to this rule
if t.typ != tokenRecipe {
return parseTopLevel(p, t)
}
return parseTopLevel
}