fix panic based error reporting
diff --git a/parser.go b/parser.go
index 06e5f74..c035791 100644
--- a/parser.go
+++ b/parser.go
@@ -23,6 +23,7 @@
"bufio"
"bytes"
"crypto/sha1"
+ "errors"
"fmt"
"io"
"io/ioutil"
@@ -48,14 +49,13 @@
lineno int
elineno int // lineno == elineno unless there is trailing '\'.
linenoFixed bool
- unBuf []byte
- hasUnBuf bool
done bool
outStmts *[]ast
ifStack []ifState
inDef []string
defOpt string
numIfNest int
+ err error
}
func newParser(rd io.Reader, filename string) *parser {
@@ -67,16 +67,18 @@
return p
}
+func (p *parser) srcpos() srcpos {
+ return srcpos{
+ filename: p.mk.filename,
+ lineno: p.lineno,
+ }
+}
+
func (p *parser) addStatement(stmt ast) {
*p.outStmts = append(*p.outStmts, stmt)
}
func (p *parser) readLine() []byte {
- if p.hasUnBuf {
- p.hasUnBuf = false
- return p.unBuf
- }
-
if !p.linenoFixed {
p.lineno = p.elineno
}
@@ -88,7 +90,8 @@
if err == io.EOF {
p.done = true
} else if err != nil {
- panic(fmt.Errorf("readline %s:%d: %v", p.mk.filename, p.lineno, err))
+ p.err = fmt.Errorf("readline %s: %v", p.srcpos(), err)
+ p.done = true
}
line = bytes.TrimRight(line, "\r\n")
@@ -163,22 +166,14 @@
return line
}
-func (p *parser) unreadLine(line []byte) {
- if p.hasUnBuf {
- panic("unreadLine twice!")
- }
- p.unBuf = line
- p.hasUnBuf = true
-}
-
-func newAssignAST(p *parser, lhsBytes []byte, rhsBytes []byte, op string) *assignAST {
+func newAssignAST(p *parser, lhsBytes []byte, rhsBytes []byte, op string) (*assignAST, error) {
lhs, _, err := parseExpr(lhsBytes, nil, true)
if err != nil {
- panic(err)
+ return nil, err
}
rhs, _, err := parseExpr(rhsBytes, nil, true)
if err != nil {
- panic(err)
+ return nil, err
}
opt := ""
if p != nil {
@@ -189,20 +184,22 @@
rhs: rhs,
op: op,
opt: opt,
- }
+ }, nil
}
-func (p *parser) parseAssign(line []byte, sep, esep int) ast {
+func (p *parser) parseAssign(line []byte, sep, esep int) (ast, error) {
logf("parseAssign %q op:%q", line, line[sep:esep])
- aast := newAssignAST(p, bytes.TrimSpace(line[:sep]), trimLeftSpaceBytes(line[esep:]), string(line[sep:esep]))
- aast.filename = p.mk.filename
- aast.lineno = p.lineno
- return aast
+ aast, err := newAssignAST(p, bytes.TrimSpace(line[:sep]), trimLeftSpaceBytes(line[esep:]), string(line[sep:esep]))
+ if err != nil {
+ return nil, err
+ }
+ aast.srcpos = p.srcpos()
+ return aast, nil
}
-func (p *parser) parseMaybeRule(line []byte, equalIndex, semicolonIndex int) ast {
+func (p *parser) parseMaybeRule(line []byte, equalIndex, semicolonIndex int) (ast, error) {
if len(trimSpaceBytes(line)) == 0 {
- return nil
+ return nil, nil
}
expr := line
@@ -229,7 +226,7 @@
v, _, err := parseExpr(expr, nil, true)
if err != nil {
- panic(fmt.Errorf("parse %s:%d %v", p.mk.filename, p.lineno, err))
+ return nil, p.srcpos().error(err)
}
rast := &maybeRuleAST{
@@ -237,106 +234,110 @@
term: term,
afterTerm: afterTerm,
}
- rast.filename = p.mk.filename
- rast.lineno = p.lineno
- return rast
+ rast.srcpos = p.srcpos()
+ return rast, nil
}
-func (p *parser) parseInclude(line string, oplen int) ast {
+func (p *parser) parseInclude(line string, oplen int) {
// TODO(ukai): parse expr here
iast := &includeAST{
expr: line[oplen+1:],
op: line[:oplen],
}
- iast.filename = p.mk.filename
- iast.lineno = p.lineno
- return iast
+ iast.srcpos = p.srcpos()
+ p.addStatement(iast)
}
-func (p *parser) parseIfdef(line []byte, oplen int) ast {
+func (p *parser) parseIfdef(line []byte, oplen int) {
lhs, _, err := parseExpr(trimLeftSpaceBytes(line[oplen+1:]), nil, true)
if err != nil {
- panic(fmt.Errorf("ifdef parse %s:%d %v", p.mk.filename, p.lineno, err))
+ p.err = p.srcpos().error(err)
+ return
}
iast := &ifAST{
op: string(line[:oplen]),
lhs: lhs,
}
- iast.filename = p.mk.filename
- iast.lineno = p.lineno
+ iast.srcpos = p.srcpos()
p.addStatement(iast)
p.ifStack = append(p.ifStack, ifState{ast: iast, numNest: p.numIfNest})
p.outStmts = &iast.trueStmts
- return iast
}
-func (p *parser) parseTwoQuotes(s string, op string) ([]string, bool) {
+func (p *parser) parseTwoQuotes(s string, op string) ([]string, bool, error) {
var args []string
for i := 0; i < 2; i++ {
s = strings.TrimSpace(s)
if s == "" {
- return nil, false
+ return nil, false, nil
}
quote := s[0]
if quote != '\'' && quote != '"' {
- return nil, false
+ return nil, false, nil
}
end := strings.IndexByte(s[1:], quote) + 1
if end < 0 {
- return nil, false
+ return nil, false, nil
}
args = append(args, s[1:end])
s = s[end+1:]
}
if len(s) > 0 {
- errorExit(p.mk.filename, p.lineno, `extraneous text after %q directive`, op)
+ return nil, false, p.srcpos().errorf(`extraneous text after %q directive`, op)
}
- return args, true
+ return args, true, nil
}
// parse
// "(lhs, rhs)"
// "lhs, rhs"
-func (p *parser) parseEq(s string, op string) (string, string, bool) {
+func (p *parser) parseEq(s string, op string) (string, string, bool, error) {
if s[0] == '(' && s[len(s)-1] == ')' {
s = s[1 : len(s)-1]
term := []byte{','}
in := []byte(s)
v, n, err := parseExpr(in, term, false)
if err != nil {
- return "", "", false
+ return "", "", false, err
}
lhs := v.String()
n++
n += skipSpaces(in[n:], nil)
v, n, err = parseExpr(in[n:], nil, false)
if err != nil {
- return "", "", false
+ return "", "", false, err
}
rhs := v.String()
- return lhs, rhs, true
+ return lhs, rhs, true, nil
}
- args, ok := p.parseTwoQuotes(s, op)
+ args, ok, err := p.parseTwoQuotes(s, op)
if !ok {
- return "", "", false
+ return "", "", false, err
}
- return args[0], args[1], true
+ return args[0], args[1], true, nil
}
-func (p *parser) parseIfeq(line string, oplen int) ast {
+func (p *parser) parseIfeq(line string, oplen int) {
op := line[:oplen]
- lhsBytes, rhsBytes, ok := p.parseEq(strings.TrimSpace(line[oplen+1:]), op)
+ lhsBytes, rhsBytes, ok, err := p.parseEq(strings.TrimSpace(line[oplen+1:]), op)
+ if err != nil {
+ p.err = err
+ return
+ }
if !ok {
- errorExit(p.mk.filename, p.lineno, `*** invalid syntax in conditional.`)
+ p.err = p.srcpos().errorf(`*** invalid syntax in conditional.`)
+ return
}
lhs, _, err := parseExpr([]byte(lhsBytes), nil, true)
if err != nil {
- panic(fmt.Errorf("parse ifeq lhs %s:%d %v", p.mk.filename, p.lineno, err))
+ p.err = p.srcpos().error(err)
+ return
}
rhs, _, err := parseExpr([]byte(rhsBytes), nil, true)
if err != nil {
- panic(fmt.Errorf("parse ifeq rhs %s:%d %v", p.mk.filename, p.lineno, err))
+ p.err = p.srcpos().error(err)
+ return
}
iast := &ifAST{
@@ -344,25 +345,30 @@
lhs: lhs,
rhs: rhs,
}
- iast.filename = p.mk.filename
- iast.lineno = p.lineno
+ iast.srcpos = p.srcpos()
p.addStatement(iast)
p.ifStack = append(p.ifStack, ifState{ast: iast, numNest: p.numIfNest})
p.outStmts = &iast.trueStmts
- return iast
+ return
}
-func (p *parser) checkIfStack(curKeyword string) {
+func (p *parser) checkIfStack(curKeyword string) error {
if len(p.ifStack) == 0 {
- errorExit(p.mk.filename, p.lineno, `*** extraneous %q.`, curKeyword)
+ return p.srcpos().errorf(`*** extraneous %q.`, curKeyword)
}
+ return nil
}
func (p *parser) parseElse(line []byte) {
- p.checkIfStack("else")
+ err := p.checkIfStack("else")
+ if err != nil {
+ p.err = err
+ return
+ }
state := &p.ifStack[len(p.ifStack)-1]
if state.inElse {
- errorExit(p.mk.filename, p.lineno, `*** only one "else" per conditional.`)
+ p.err = p.srcpos().errorf(`*** only one "else" per conditional.`)
+ return
}
state.inElse = true
p.outStmts = &state.ast.falseStmts
@@ -384,11 +390,16 @@
return
}
p.numIfNest = 0
- warnNoPrefix(p.mk.filename, p.lineno, "extraneous text after `else` directive")
+ warnNoPrefix(p.srcpos(), "extraneous text after `else` directive")
+ return
}
func (p *parser) parseEndif(line string) {
- p.checkIfStack("endif")
+ err := p.checkIfStack("endif")
+ if err != nil {
+ p.err = err
+ return
+ }
state := p.ifStack[len(p.ifStack)-1]
for t := 0; t <= state.numNest; t++ {
p.ifStack = p.ifStack[0 : len(p.ifStack)-1]
@@ -403,6 +414,7 @@
}
}
}
+ return
}
type directiveFunc func(*parser, []byte) []byte
@@ -447,12 +459,12 @@
}
func includeDirective(p *parser, line []byte) []byte {
- p.addStatement(p.parseInclude(string(line), len("include")))
+ p.parseInclude(string(line), len("include"))
return nil
}
func sincludeDirective(p *parser, line []byte) []byte {
- p.addStatement(p.parseInclude(string(line), len("-include")))
+ p.parseInclude(string(line), len("-include"))
return nil
}
@@ -522,8 +534,7 @@
expr: line,
export: export,
}
- east.filename = p.mk.filename
- east.lineno = p.lineno
+ east.srcpos = p.srcpos()
p.addStatement(east)
return hasEqual
}
@@ -561,7 +572,7 @@
if found >= 0 && s[:found] == "endef" {
rest := strings.TrimSpace(s[found+1:])
if rest != "" && rest[0] != '#' {
- warnNoPrefix(p.mk.filename, p.lineno, "extraneous text after \"endef\" directive")
+ warnNoPrefix(p.srcpos(), "extraneous text after \"endef\" directive")
}
return true
}
@@ -569,11 +580,6 @@
}
func (p *parser) parse() (mk makefile, err error) {
- defer func() {
- if r := recover(); r != nil {
- err = fmt.Errorf("panic in parse %s: %v", mk.filename, r)
- }
- }()
for !p.done {
line := p.readLine()
@@ -581,9 +587,12 @@
lineStr := string(p.processDefineLine(line))
if p.isEndef(lineStr) {
logf("multilineAssign %q", p.inDef)
- aast := newAssignAST(p, []byte(p.inDef[0]), []byte(strings.Join(p.inDef[1:], "\n")), "=")
- aast.filename = p.mk.filename
- aast.lineno = p.lineno - len(p.inDef)
+ aast, err := newAssignAST(p, []byte(p.inDef[0]), []byte(strings.Join(p.inDef[1:], "\n")), "=")
+ if err != nil {
+ return makefile{}, err
+ }
+ aast.srcpos = p.srcpos()
+ aast.srcpos.lineno -= len(p.inDef)
p.addStatement(aast)
p.inDef = nil
p.defOpt = ""
@@ -601,14 +610,16 @@
if f, ok := p.isDirective(line, makeDirectives); ok {
line = trimSpaceBytes(p.processMakefileLine(line))
line = f(p, line)
+ if p.err != nil {
+ return makefile{}, p.err
+ }
if len(line) == 0 {
continue
}
}
if line[0] == '\t' {
cast := &commandAST{cmd: string(p.processRecipeLine(line[1:]))}
- cast.filename = p.mk.filename
- cast.lineno = p.lineno
+ cast.srcpos = p.srcpos()
p.addStatement(cast)
continue
}
@@ -626,7 +637,7 @@
parenStack = append(parenStack, ch)
case ')', '}':
if len(parenStack) == 0 {
- warn(p.mk.filename, p.lineno, "Unmatched parens: %s", line)
+ warn(p.srcpos(), "Unmatched parens: %s", line)
} else {
cp := closeParen(parenStack[len(parenStack)-1])
if cp == ch {
@@ -642,7 +653,10 @@
case ':':
if i+1 < len(line) && line[i+1] == '=' {
if !isRule {
- stmt = p.parseAssign(line, i, i+2)
+ stmt, err = p.parseAssign(line, i, i+2)
+ if err != nil {
+ return makefile{}, err
+ }
}
} else {
isRule = true
@@ -653,14 +667,20 @@
}
case '=':
if !isRule {
- stmt = p.parseAssign(line, i, i+1)
+ stmt, err = p.parseAssign(line, i, i+1)
+ if err != nil {
+ return makefile{}, err
+ }
}
if equalIndex < 0 {
equalIndex = i
}
case '?', '+':
if !isRule && i+1 < len(line) && line[i+1] == '=' {
- stmt = p.parseAssign(line, i, i+2)
+ stmt, err = p.parseAssign(line, i, i+2)
+ if err != nil {
+ return makefile{}, err
+ }
}
}
if stmt != nil {
@@ -669,40 +689,42 @@
}
}
if stmt == nil {
- stmt = p.parseMaybeRule(line, equalIndex, semicolonIndex)
+ stmt, err = p.parseMaybeRule(line, equalIndex, semicolonIndex)
+ if err != nil {
+ return makefile{}, err
+ }
if stmt != nil {
p.addStatement(stmt)
}
}
}
- return p.mk, nil
+ return p.mk, p.err
}
-func defaultMakefile() string {
+func defaultMakefile() (string, error) {
candidates := []string{"GNUmakefile", "makefile", "Makefile"}
for _, filename := range candidates {
if exists(filename) {
- return filename
+ return filename, nil
}
}
- errorNoLocationExit("no targets specified and no makefile found.")
- panic("") // Cannot be reached.
+ return "", errors.New("no targets specified and no makefile found")
}
-func parseMakefileReader(rd io.Reader, name string, lineno int) (makefile, error) {
- parser := newParser(rd, name)
- parser.lineno = lineno
- parser.elineno = lineno
+func parseMakefileReader(rd io.Reader, loc srcpos) (makefile, error) {
+ parser := newParser(rd, loc.filename)
+ parser.lineno = loc.lineno
+ parser.elineno = loc.lineno
parser.linenoFixed = true
return parser.parse()
}
-func parseMakefileString(s string, name string, lineno int) (makefile, error) {
- return parseMakefileReader(strings.NewReader(s), name, lineno)
+func parseMakefileString(s string, loc srcpos) (makefile, error) {
+ return parseMakefileReader(strings.NewReader(s), loc)
}
-func parseMakefileBytes(s []byte, name string, lineno int) (makefile, error) {
- return parseMakefileReader(bytes.NewReader(s), name, lineno)
+func parseMakefileBytes(s []byte, loc srcpos) (makefile, error) {
+ return parseMakefileReader(bytes.NewReader(s), loc)
}
type mkCacheEntry struct {