mk/parse.go

314 lines
7.2 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"
"os"
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
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) {
fmt.Fprintf(os.Stderr, "%s:%d: syntax error: ", p.name, found.line)
fmt.Fprintf(os.Stderr, "while %s, expected %s but found \"%s\".\n",
context, expected, found.String())
os.Exit(1)
}
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) {
fmt.Fprintf(os.Stderr, "%s:%d: syntax error: %s\n",
p.name, line, what)
os.Exit(1)
}
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.
2013-02-26 11:33:07 -08:00
func parse(input string, name string) *ruleSet {
rules := &ruleSet{make(map[string][]string), make([]rule, 0)}
parseInto(input, name, rules)
return rules
2013-02-25 23:52:08 -08:00
}
// Parse a mkfile inserting rules and variables into a given ruleSet.
2013-02-26 11:33:07 -08:00
func parseInto(input string, name string, rules *ruleSet) {
l, tokens := lex(input)
p := &parser{l, name, []token{}, rules}
state := parseTopLevel
for t := range tokens {
if t.typ == tokenError {
// TODO: fancier error messages
fmt.Fprintf(os.Stderr, "Error: %s", l.errmsg)
break
}
state = state(p, t)
}
// insert a dummy newline to allow parsing of any assignments or recipeless
// rules to finish.
state = state(p, token{tokenNewline, "\n", l.line})
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)
}
args := make([]string, len(p.tokenbuf)-1)
for i := 1; i < len(p.tokenbuf); i++ {
args[i-1] = p.tokenbuf[i].val
}
output := executeRecipe("sh", args, "", false, false, true)
parseInto(output, fmt.Sprintf("%s:sh", p.name), p.rules)
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:
// TODO: Complain about unexpected tokens.
}
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:
// TODO:
// Open the file, read its context, call parseInto recursively.
// Clear out p.tokenbuf
2013-02-25 23:52:08 -08:00
2013-02-26 22:41:25 -08:00
case tokenWord:
// TODO:
2013-02-25 23:52:08 -08:00
2013-02-26 11:33:07 -08:00
default:
// TODO: Complain about unexpected tokens.
}
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 {
fmt.Println("assignment or target")
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 {
fmt.Println("equals or target")
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:
p.parseError("reading a a target or assignment",
"'=', ':', 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
2013-02-26 22:41:25 -08:00
// Consumed one or more strings followed by a first ':'.
2013-02-26 11:33:07 -08:00
func parseAttributesOrPrereqs(p *parser, t token) parserStateFun {
fmt.Println("attributes or prereqs")
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 {
fmt.Println("prereqs")
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 {
fmt.Println("recipe")
// 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++ {
}
// targets
r.targets = make([]string, i)
for k := 0; k < i; k++ {
2013-02-28 22:49:34 -08:00
r.targets[k] = p.rules.expand(p.tokenbuf[k].val, true)
2013-02-26 11:33:07 -08:00
}
// rule has attributes
2013-02-28 22:49:34 -08:00
// TODO: should we be expanding the attribute strings?
2013-02-26 11:33:07 -08:00
if j < len(p.tokenbuf) {
attribs := make([]string, j-i-1)
for k := i + 1; k < j; k++ {
attribs[k-i-1] = p.tokenbuf[k].val
}
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])
}
} else {
j = i
}
// prereqs
r.prereqs = make([]string, len(p.tokenbuf)-j-1)
for k := j + 1; k < len(p.tokenbuf); k++ {
2013-02-28 22:49:34 -08:00
r.prereqs[k-j-1] = p.rules.expand(p.tokenbuf[k].val, true)
2013-02-26 11:33:07 -08:00
}
if t.typ == tokenRecipe {
2013-02-28 22:49:34 -08:00
r.recipe = p.rules.expand(t.val, false)
2013-02-26 11:33:07 -08:00
}
p.rules.push(r)
p.clear()
// the current token doesn't belong to this rule
if t.typ != tokenRecipe {
return parseTopLevel(p, t)
}
return parseTopLevel
}