]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile/internal/syntax: fix error handling for Read/Parse calls
authorRobert Griesemer <gri@golang.org>
Wed, 9 Nov 2016 00:01:56 +0000 (16:01 -0800)
committerRobert Griesemer <gri@golang.org>
Wed, 9 Nov 2016 17:48:40 +0000 (17:48 +0000)
- define syntax.Error for cleaner error reporting
- abort parsing after first error if no error handler is installed
- make sure to always report the first error, if any
- document behavior of API calls
- while at it: rename ReadXXX -> ParseXXX (clearer)
- adjust cmd/compile noder.go accordingly

Fixes #17774.

Change-Id: I7893eedea454a64acd753e32f7a8bf811ddbb03c
Reviewed-on: https://go-review.googlesource.com/32950
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
src/cmd/compile/internal/gc/noder.go
src/cmd/compile/internal/syntax/dumper_test.go
src/cmd/compile/internal/syntax/parser.go
src/cmd/compile/internal/syntax/parser_test.go
src/cmd/compile/internal/syntax/printer_test.go
src/cmd/compile/internal/syntax/scanner_test.go
src/cmd/compile/internal/syntax/source.go
src/cmd/compile/internal/syntax/syntax.go

index 0189242d181159ec16592ddd9bac5807ba44a088..35cbeb5a25b3f9d5172843365cecbb451220da98 100644 (file)
@@ -6,6 +6,7 @@ package gc
 
 import (
        "fmt"
+       "os"
        "strconv"
        "strings"
        "unicode/utf8"
@@ -14,18 +15,21 @@ import (
 )
 
 func parseFile(filename string) {
-       p := noder{baseline: lexlineno}
-       file, err := syntax.ReadFile(filename, p.error, p.pragma, 0)
+       src, err := os.Open(filename)
        if err != nil {
-               fmt.Printf("parse %s: %v\n", filename, err)
+               fmt.Println(err)
                errorexit()
        }
+       defer src.Close()
+
+       p := noder{baseline: lexlineno}
+       file, _ := syntax.Parse(src, p.error, p.pragma, 0) // errors are tracked via p.error
 
        p.file(file)
 
        if !imported_unsafe {
                for _, x := range p.linknames {
-                       p.error(0, x, "//go:linkname only allowed in Go files that import \"unsafe\"")
+                       p.error(syntax.Error{0, x, "//go:linkname only allowed in Go files that import \"unsafe\""})
                }
        }
 
@@ -1003,8 +1007,16 @@ func (p *noder) lineno(n syntax.Node) {
        lineno = p.baseline + l - 1
 }
 
-func (p *noder) error(_, line int, msg string) {
-       yyerrorl(p.baseline+int32(line)-1, "%s", msg)
+func (p *noder) error(err error) {
+       line := p.baseline
+       var msg string
+       if err, ok := err.(syntax.Error); ok {
+               line += int32(err.Line) - 1
+               msg = err.Msg
+       } else {
+               msg = err.Error()
+       }
+       yyerrorl(line, "%s", msg)
 }
 
 func (p *noder) pragma(pos, line int, text string) syntax.Pragma {
@@ -1020,7 +1032,7 @@ func (p *noder) pragma(pos, line int, text string) syntax.Pragma {
                        break
                }
                if n > 1e8 {
-                       p.error(pos, line, "line number out of range")
+                       p.error(syntax.Error{pos, line, "line number out of range"})
                        errorexit()
                }
                if n <= 0 {
@@ -1036,7 +1048,7 @@ func (p *noder) pragma(pos, line int, text string) syntax.Pragma {
 
                f := strings.Fields(text)
                if len(f) != 3 {
-                       p.error(pos, line, "usage: //go:linkname localname linkname")
+                       p.error(syntax.Error{pos, line, "usage: //go:linkname localname linkname"})
                        break
                }
                lookup(f[1]).Linkname = f[2]
index 1ee1d982d0b6990e5986bf180944a1aa1316117a..2b20cbdd97f03cccb4b4fa98f6aa61a53976705e 100644 (file)
@@ -14,7 +14,7 @@ func TestDump(t *testing.T) {
                t.Skip("skipping test in short mode")
        }
 
-       ast, err := ReadFile(*src, nil, nil, 0)
+       ast, err := ParseFile(*src, nil, nil, 0)
        if err != nil {
                t.Fatal(err)
        }
index a959c6cd2588878cdb6f277ae2a19103186d47bc..41e7cbe56d7e6314bc8f0c7a6964fa02c86d9896 100644 (file)
@@ -24,27 +24,16 @@ type parser struct {
        fnest  int    // function nesting level (for error handling)
        xnest  int    // expression nesting level (for complit ambiguity resolution)
        indent []byte // tracing support
-
-       nerrors int // error count
 }
 
 type parserError string // for error recovery if no error handler was installed
 
 func (p *parser) init(src io.Reader, errh ErrorHandler, pragh PragmaHandler) {
-       p.scanner.init(src, func(pos, line int, msg string) {
-               p.nerrors++
-               if !debug && errh != nil {
-                       errh(pos, line, msg)
-                       return
-               }
-               panic(parserError(fmt.Sprintf("%d: %s\n", line, msg)))
-       }, pragh)
+       p.scanner.init(src, errh, pragh)
 
        p.fnest = 0
        p.xnest = 0
        p.indent = nil
-
-       p.nerrors = 0
 }
 
 func (p *parser) got(tok token) bool {
@@ -76,7 +65,7 @@ func (p *parser) syntax_error_at(pos, line int, msg string) {
                defer p.trace("syntax_error (" + msg + ")")()
        }
 
-       if p.tok == _EOF && p.nerrors > 0 {
+       if p.tok == _EOF && p.first != nil {
                return // avoid meaningless follow-up errors
        }
 
@@ -207,7 +196,7 @@ func (p *parser) file() *File {
        p.want(_Semi)
 
        // don't bother continuing if package clause has errors
-       if p.nerrors > 0 {
+       if p.first != nil {
                return nil
        }
 
index 0eb9cd5eb362274a966aa0dfdec249141a90cc47..dc7f91d5efcbf6da056ad0e4a589381bacf169f5 100644 (file)
@@ -22,7 +22,7 @@ var src = flag.String("src", "parser.go", "source file to parse")
 var verify = flag.Bool("verify", false, "verify idempotent printing")
 
 func TestParse(t *testing.T) {
-       _, err := ReadFile(*src, nil, nil, 0)
+       _, err := ParseFile(*src, nil, nil, 0)
        if err != nil {
                t.Fatal(err)
        }
@@ -52,7 +52,7 @@ func TestStdLib(t *testing.T) {
                                if debug {
                                        fmt.Printf("parsing %s\n", filename)
                                }
-                               ast, err := ReadFile(filename, nil, nil, 0)
+                               ast, err := ParseFile(filename, nil, nil, 0)
                                if err != nil {
                                        t.Error(err)
                                        return
@@ -133,7 +133,7 @@ func verifyPrint(filename string, ast1 *File) {
                panic(err)
        }
 
-       ast2, err := ReadBytes(buf1.Bytes(), nil, nil, 0)
+       ast2, err := ParseBytes(buf1.Bytes(), nil, nil, 0)
        if err != nil {
                panic(err)
        }
@@ -157,8 +157,28 @@ func verifyPrint(filename string, ast1 *File) {
 }
 
 func TestIssue17697(t *testing.T) {
-       _, err := ReadBytes(nil, nil, nil, 0) // return with parser error, don't panic
+       _, err := ParseBytes(nil, nil, nil, 0) // return with parser error, don't panic
        if err == nil {
                t.Errorf("no error reported")
        }
 }
+
+func TestParseFile(t *testing.T) {
+       _, err := ParseFile("", nil, nil, 0)
+       if err == nil {
+               t.Error("missing io error")
+       }
+
+       var first error
+       _, err = ParseFile("", func(err error) {
+               if first == nil {
+                       first = err
+               }
+       }, nil, 0)
+       if err == nil || first == nil {
+               t.Error("missing io error")
+       }
+       if err != first {
+               t.Error("got %v; want first error %v", err, first)
+       }
+}
index a2d43068ddc0c27c2e13db62d6045aae42060200..5c0fc776a1c85df200760655212b440c8e1cb1b4 100644 (file)
@@ -15,7 +15,7 @@ func TestPrint(t *testing.T) {
                t.Skip("skipping test in short mode")
        }
 
-       ast, err := ReadFile(*src, nil, nil, 0)
+       ast, err := ParseFile(*src, nil, nil, 0)
        if err != nil {
                t.Fatal(err)
        }
index 38a7e0da4c6209282313e3e9a082fbd9484e7ef4..0e81c4e6133a64870a0f5b73742e3c55dbc31f87 100644 (file)
@@ -322,21 +322,22 @@ func TestScanErrors(t *testing.T) {
        } {
                var s scanner
                nerrors := 0
-               s.init(&bytesReader{[]byte(test.src)}, func(pos, line int, msg string) {
+               s.init(&bytesReader{[]byte(test.src)}, func(err error) {
                        nerrors++
                        // only check the first error
+                       e := err.(Error) // we know it's an Error
                        if nerrors == 1 {
-                               if msg != test.msg {
-                                       t.Errorf("%q: got msg = %q; want %q", test.src, msg, test.msg)
+                               if e.Msg != test.msg {
+                                       t.Errorf("%q: got msg = %q; want %q", test.src, e.Msg, test.msg)
                                }
-                               if pos != test.pos {
-                                       t.Errorf("%q: got pos = %d; want %d", test.src, pos, test.pos)
+                               if e.Pos != test.pos {
+                                       t.Errorf("%q: got pos = %d; want %d", test.src, e.Pos, test.pos)
                                }
-                               if line != test.line {
-                                       t.Errorf("%q: got line = %d; want %d", test.src, line, test.line)
+                               if e.Line != test.line {
+                                       t.Errorf("%q: got line = %d; want %d", test.src, e.Line, test.line)
                                }
                        } else if nerrors > 1 {
-                               t.Errorf("%q: got unexpected %q at pos = %d, line = %d", test.src, msg, pos, line)
+                               t.Errorf("%q: got unexpected %q at pos = %d, line = %d", test.src, e.Msg, e.Pos, e.Line)
                        }
                }, nil)
 
index 87c22fcc268357d9483a0b8672add5c44314efe5..05a11960c6bc874d53681f19ec616de1e3b600d2 100644 (file)
@@ -5,7 +5,6 @@
 package syntax
 
 import (
-       "fmt"
        "io"
        "unicode/utf8"
 )
@@ -16,8 +15,9 @@ import (
 //        suf     r0  r            w
 
 type source struct {
-       src  io.Reader
-       errh ErrorHandler
+       src   io.Reader
+       errh  ErrorHandler
+       first error // first error encountered
 
        // source buffer
        buf         [4 << 10]byte
@@ -34,6 +34,7 @@ type source struct {
 func (s *source) init(src io.Reader, errh ErrorHandler) {
        s.src = src
        s.errh = errh
+       s.first = nil
 
        s.buf[0] = utf8.RuneSelf // terminate with sentinel
        s.offs = 0
@@ -50,11 +51,14 @@ func (s *source) error(msg string) {
 }
 
 func (s *source) error_at(pos, line int, msg string) {
-       if s.errh != nil {
-               s.errh(pos, line, msg)
-               return
+       err := Error{pos, line, msg}
+       if s.first == nil {
+               s.first = err
        }
-       panic(fmt.Sprintf("%d: %s", line, msg))
+       if s.errh == nil {
+               panic(s.first)
+       }
+       s.errh(err)
 }
 
 // pos0 returns the byte position of the last character read.
index 71fc097c3b2ec14a64b16cd543fbbacc7f855107..b1e56ee946a81743025fe73b126074511136b547 100644 (file)
@@ -5,35 +5,72 @@
 package syntax
 
 import (
-       "errors"
        "fmt"
        "io"
        "os"
 )
 
+// Mode describes the parser mode.
 type Mode uint
 
+// Error describes a syntax error. Error implements the error interface.
+type Error struct {
+       // TODO(gri) decide what we really need here
+       Pos  int // byte offset from file start
+       Line int // line (starting with 1)
+       Msg  string
+}
+
+func (err Error) Error() string {
+       return fmt.Sprintf("%d: %s", err.Line, err.Msg)
+}
+
+var _ error = Error{} // verify that Error implements error
+
+// An ErrorHandler is called for each error encountered reading a .go file.
+type ErrorHandler func(err error)
+
 // A Pragma value is a set of flags that augment a function or
 // type declaration. Callers may assign meaning to the flags as
 // appropriate.
 type Pragma uint16
 
-type ErrorHandler func(pos, line int, msg string)
-
 // A PragmaHandler is used to process //line and //go: directives as
 // they're scanned. The returned Pragma value will be unioned into the
 // next FuncDecl node.
 type PragmaHandler func(pos, line int, text string) Pragma
 
-// TODO(gri) These need a lot more work.
+// Parse parses a single Go source file from src and returns the corresponding
+// syntax tree. If there are syntax errors, Parse will return the first error
+// encountered.
+//
+// If errh != nil, it is called with each error encountered, and Parse will
+// process as much source as possible. If errh is nil, Parse will terminate
+// immediately upon encountering an error.
+//
+// If a PragmaHandler is provided, it is called with each pragma encountered.
+//
+// The Mode argument is currently ignored.
+func Parse(src io.Reader, errh ErrorHandler, pragh PragmaHandler, mode Mode) (_ *File, err error) {
+       defer func() {
+               if p := recover(); p != nil {
+                       var ok bool
+                       if err, ok = p.(Error); ok {
+                               return
+                       }
+                       panic(p)
+               }
+       }()
 
-func ReadFile(filename string, errh ErrorHandler, pragh PragmaHandler, mode Mode) (*File, error) {
-       src, err := os.Open(filename)
-       if err != nil {
-               return nil, err
-       }
-       defer src.Close()
-       return Read(src, errh, pragh, mode)
+       var p parser
+       p.init(src, errh, pragh)
+       p.next()
+       return p.file(), p.first
+}
+
+// ParseBytes behaves like Parse but it reads the source from the []byte slice provided.
+func ParseBytes(src []byte, errh ErrorHandler, pragh PragmaHandler, mode Mode) (*File, error) {
+       return Parse(&bytesReader{src}, errh, pragh, mode)
 }
 
 type bytesReader struct {
@@ -49,37 +86,15 @@ func (r *bytesReader) Read(p []byte) (int, error) {
        return 0, io.EOF
 }
 
-func ReadBytes(src []byte, errh ErrorHandler, pragh PragmaHandler, mode Mode) (*File, error) {
-       return Read(&bytesReader{src}, errh, pragh, mode)
-}
-
-func Read(src io.Reader, errh ErrorHandler, pragh PragmaHandler, mode Mode) (ast *File, err error) {
-       defer func() {
-               if p := recover(); p != nil {
-                       if msg, ok := p.(parserError); ok {
-                               err = errors.New(string(msg))
-                               return
-                       }
-                       panic(p)
+// ParseFile behaves like Parse but it reads the source from the named file.
+func ParseFile(filename string, errh ErrorHandler, pragh PragmaHandler, mode Mode) (*File, error) {
+       src, err := os.Open(filename)
+       if err != nil {
+               if errh != nil {
+                       errh(err)
                }
-       }()
-
-       var p parser
-       p.init(src, errh, pragh)
-       p.next()
-       ast = p.file()
-
-       // TODO(gri) This isn't quite right: Even if there's an error handler installed
-       //           we should report an error if parsing found syntax errors. This also
-       //           requires updating the noder's ReadFile call.
-       if errh == nil && p.nerrors > 0 {
-               ast = nil
-               err = fmt.Errorf("%d syntax errors", p.nerrors)
+               return nil, err
        }
-
-       return
-}
-
-func Write(w io.Writer, n *File) error {
-       panic("unimplemented")
+       defer src.Close()
+       return Parse(src, errh, pragh, mode)
 }