import (
"fmt"
+ "os"
"strconv"
"strings"
"unicode/utf8"
)
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\""})
}
}
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 {
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 {
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]
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)
}
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 {
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
}
p.want(_Semi)
// don't bother continuing if package clause has errors
- if p.nerrors > 0 {
+ if p.first != nil {
return nil
}
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)
}
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
panic(err)
}
- ast2, err := ReadBytes(buf1.Bytes(), nil, nil, 0)
+ ast2, err := ParseBytes(buf1.Bytes(), nil, nil, 0)
if err != nil {
panic(err)
}
}
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)
+ }
+}
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)
}
} {
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)
package syntax
import (
- "fmt"
"io"
"unicode/utf8"
)
// 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
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
}
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.
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 {
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)
}