return dollarString(string(s), "(", ")");
}
-// TODO(rsc): parse.Parse should return an os.Error.
-var ParseError = os.NewError("parse errors");
-
// TODO(rsc): Should this be in the AST library?
func LitString(p []*ast.StringLit) (string, os.Error) {
s := "";
return "", nil, err
}
- prog, ok := parser.Parse(f, nil, parser.ImportsOnly);
- if !ok {
- return "", nil, ParseError;
+ prog, err := parser.Parse(f, parser.ImportsOnly);
+ if err != nil {
+ return "", nil, err;
}
// Normally one must consult the types of decl and spec,
for _, spec := range decl.(*ast.GenDecl).Specs {
str, err := LitString(spec.(*ast.ImportSpec).Path);
if err != nil {
- return "", nil, ParseError; // ParseError is better than os.EINVAL
+ return "", nil, os.NewError("invalid import specifier"); // better than os.EINVAL
}
PushString(&imp, str);
}
"go/scanner";
"go/token";
"io";
+ "os";
)
-// An implementation of an ErrorHandler may be provided to the parser.
-// If a syntax error is encountered and a handler was installed, Error
-// is called with a position and an error message. The position points
-// to the beginning of the offending token.
+// A parser error is represented by an Error node. The position Pos, if
+// valid, points to the beginning of the offending token, and the error
+// condition is described by Msg.
//
-type ErrorHandler interface {
- Error(pos token.Position, msg string);
+type Error struct {
+ Pos token.Position;
+ Msg string;
+}
+
+
+func (e *Error) String() string {
+ pos := "";
+ if e.Pos.IsValid() {
+ pos = fmt.Sprintf("%d:%d: ", e.Pos.Line, e.Pos.Column);
+ }
+ return pos + e.Msg;
+}
+
+
+// Parser errors are returned as an ErrorList.
+type ErrorList []*Error
+
+
+// ErrorList implements the SortInterface.
+func (p ErrorList) Len() int { return len(p); }
+func (p ErrorList) Swap(i, j int) { p[i], p[j] = p[j], p[i]; }
+func (p ErrorList) Less(i, j int) bool { return p[i].Pos.Offset < p[j].Pos.Offset; }
+
+
+func (p ErrorList) String() string {
+ return fmt.Sprintf("%d syntax errors", len(p));
}
// The parser structure holds the parser's internal state.
type parser struct {
+ errors vector.Vector;
scanner scanner.Scanner;
- err ErrorHandler; // nil if no handler installed
- hasErrors bool;
// Tracing/debugging
mode uint; // parsing mode
}
-func (p *parser) error(pos token.Position, msg string) {
- if p.err != nil {
- p.err.Error(pos, msg);
+// The parser implements scanner.Error.
+func (p *parser) Error(pos token.Position, msg string) {
+ // Don't collect errors that are on the same line as the previous error
+ // in the hope to reduce the number of spurious errors due to incorrect
+ // parser synchronization.
+ if p.errors.Len() == 0 || p.errors.Last().(*Error).Pos.Line != pos.Line {
+ p.errors.Push(&Error{pos, msg});
}
- p.hasErrors = true;
}
msg += " " + string(p.lit);
}
}
- p.error(pos, msg);
+ p.Error(pos, msg);
}
p.next();
if p.tok != token.RPAREN {
// "..." always must be at the very end of a parameter list
- p.error(pos, "expected type, found '...'");
+ p.Error(pos, "expected type, found '...'");
}
return &ast.Ellipsis{pos};
}
}
case *ast.ArrayType:
if len, is_ellipsis := t.Len.(*ast.Ellipsis); is_ellipsis {
- p.error(len.Pos(), "expected array length, found '...'");
+ p.Error(len.Pos(), "expected array length, found '...'");
x = &ast.BadExpr{x.Pos()};
}
}
return &ast.LabeledStmt{label, p.parseStatement()};
}
}
- p.error(x[0].Pos(), "illegal label declaration");
+ p.Error(x[0].Pos(), "illegal label declaration");
return &ast.BadStmt{x[0].Pos()};
case
p.next();
y := p.parseExpressionList();
if len(x) > 1 && len(y) > 1 && len(x) != len(y) {
- p.error(x[0].Pos(), "arity of lhs doesn't match rhs");
+ p.Error(x[0].Pos(), "arity of lhs doesn't match rhs");
}
return &ast.AssignStmt{x, pos, tok, y};
}
if len(x) > 1 {
- p.error(x[0].Pos(), "only one expression allowed");
+ p.Error(x[0].Pos(), "only one expression allowed");
// continue with first expression
}
if es, is_expr := s.(*ast.ExprStmt); is_expr {
return p.checkExpr(es.X);
}
- p.error(s.Pos(), "expected condition, found simple statement");
+ p.Error(s.Pos(), "expected condition, found simple statement");
return &ast.BadExpr{s.Pos()};
}
// Don't bother parsing the rest if we had errors already.
// Likely not a Go source file at all.
- if !p.hasErrors && p.mode & PackageClauseOnly == 0 {
+ if p.errors.Len() == 0 && p.mode & PackageClauseOnly == 0 {
// import decls
list := vector.New(0);
for p.tok == token.IMPORT {
// ----------------------------------------------------------------------------
// Parsing of entire programs.
-func readSource(src interface{}, err ErrorHandler) []byte {
- errmsg := "invalid input type (or nil)";
-
- switch s := src.(type) {
- case string:
- return io.StringBytes(s);
- case []byte:
- return s;
- case *io.ByteBuffer:
- // is io.Read, but src is already available in []byte form
- if s != nil {
- return s.Data();
- }
- case io.Reader:
- var buf io.ByteBuffer;
- n, os_err := io.Copy(s, &buf);
- if os_err == nil {
- return buf.Data();
+func readSource(src interface{}) ([]byte, os.Error) {
+ if src != nil {
+ switch s := src.(type) {
+ case string:
+ return io.StringBytes(s), nil;
+ case []byte:
+ return s, nil;
+ case *io.ByteBuffer:
+ // is io.Read, but src is already available in []byte form
+ if s != nil {
+ return s.Data(), nil;
+ }
+ case io.Reader:
+ var buf io.ByteBuffer;
+ n, err := io.Copy(s, &buf);
+ if err != nil {
+ return nil, err;
+ }
+ return buf.Data(), nil;
}
- errmsg = os_err.String();
}
-
- if err != nil {
- err.Error(noPos, errmsg);
- }
- return nil;
+ return nil, os.ErrorString("invalid source");
}
//
// The program source src may be provided in a variety of formats. At the
// moment the following types are supported: string, []byte, and io.Read.
+// The mode parameter controls the amount of source text parsed and other
+// optional parser functionality.
//
-// The ErrorHandler err, if not nil, is invoked if src cannot be read and
-// for each syntax error found. The mode parameter controls the amount of
-// source text parsed and other optional parser functionality.
-//
-// Parse returns an AST and the boolean value true if no errors occured;
-// it returns a partial AST (or nil if the source couldn't be read) and
-// the boolean value false to indicate failure.
+// Parse returns a complete AST if no error occured. Otherwise, if the
+// source couldn't be read, the returned program is nil and the error
+// indicates the specific failure. If the source was read but syntax
+// errors were found, the result is a partial AST (with ast.BadX nodes
+// representing the fragments of erroneous source code) and an ErrorList
+// describing the syntax errors.
//
-// If syntax errors were found, the AST may only be constructed partially,
-// with ast.BadX nodes representing the fragments of erroneous source code.
-//
-func Parse(src interface{}, err ErrorHandler, mode uint) (*ast.Program, bool) {
- data := readSource(src, err);
+func Parse(src interface{}, mode uint) (*ast.Program, os.Error) {
+ data, err := readSource(src);
+ if err != nil {
+ return nil, err;
+ }
// initialize parser state
var p parser;
- p.scanner.Init(data, err, mode & ParseComments != 0);
- p.err = err;
+ p.errors.Init(0);
+ p.scanner.Init(data, &p, mode & ParseComments != 0);
p.mode = mode;
p.trace = mode & Trace != 0; // for convenience (p.trace is used frequently)
p.comments.Init(0);
// parse program
prog := p.parsePackage();
- return prog, p.scanner.ErrorCount == 0 && !p.hasErrors;
+ // convert errors list, if any
+ if p.errors.Len() > 0 {
+ errors := make(ErrorList, p.errors.Len());
+ for i := 0; i < p.errors.Len(); i++ {
+ errors[i] = p.errors.At(i).(*Error);
+ }
+ return prog, errors;
+ }
+
+ return prog, nil;
}
)
-func TestParse0(t *testing.T) {
- // test nil []bytes source
- var src []byte;
- prog, ok := Parse(src, nil, 0);
- if ok {
- t.Errorf("parse should have failed");
- }
+var illegalInputs = []interface{} {
+ nil,
+ 3.14,
+ []byte(nil),
+ "foo!",
}
-func TestParse1(t *testing.T) {
- // test string source
- src := `package main import "fmt" func main() { fmt.Println("Hello, World!") }`;
- prog, ok := Parse(src, nil, 0);
- if !ok {
- t.Errorf("parse failed");
+func TestParseIllegalInputs(t *testing.T) {
+ for _, src := range illegalInputs {
+ prog, err := Parse(src, 0);
+ if err == nil {
+ t.Errorf("Parse(%v) should have failed", src);
+ }
}
}
-func TestParse2(t *testing.T) {
- // test io.Read source
- filename := "parser_test.go";
- src, err := os.Open(filename, os.O_RDONLY, 0);
- defer src.Close();
- if err != nil {
- t.Errorf("cannot open %s (%s)\n", filename, err.String());
+
+var validPrograms = []interface{} {
+ `package main`,
+ `package main import "fmt" func main() { fmt.Println("Hello, World!") }`,
+}
+
+
+func TestParseValidPrograms(t *testing.T) {
+ for _, src := range validPrograms {
+ prog, err := Parse(src, 0);
+ if err != nil {
+ t.Errorf("Parse(%q) failed: %v", src, err);
+ }
}
+}
+
+
+var validFiles = []string {
+ "parser.go",
+ "parser_test.go",
+}
+
+
+func TestParse3(t *testing.T) {
+ for _, filename := range validFiles {
+ src, err := os.Open(filename, os.O_RDONLY, 0);
+ defer src.Close();
+ if err != nil {
+ t.Fatalf("os.Open(%s): %v\n", filename, err);
+ }
- prog, ok := Parse(src, nil, 0);
- if !ok {
- t.Errorf("parse failed");
+ prog, err := Parse(src, 0);
+ if err != nil {
+ t.Errorf("Parse(%q): %v", src, err);
+ }
}
}
// Token source positions are represented by a Position value.
+// A Position is valid if the line number is > 0.
+//
type Position struct {
Offset int; // byte offset, starting at 0
Line int; // line number, starting at 1
func (pos *Position) Pos() Position {
return *pos;
}
+
+
+// IsValid returns true if the position is valid.
+func (pos *Position) IsValid() bool {
+ return pos.Line > 0
+}
// ----------------------------------------------------------------------------
// Parsing
-type rawError struct {
- pos token.Position;
- msg string;
-}
-
-
-type rawErrorVector struct {
- vector.Vector;
-}
-
-
-func (v *rawErrorVector) At(i int) rawError { return v.Vector.At(i).(rawError) }
-func (v *rawErrorVector) Less(i, j int) bool { return v.At(i).pos.Offset < v.At(j).pos.Offset; }
-
-
-func (v *rawErrorVector) Error(pos token.Position, msg string) {
- // only collect errors that are on a new line
- // in the hope to avoid most follow-up errors
- lastLine := 0;
- if n := v.Len(); n > 0 {
- lastLine = v.At(n - 1).pos.Line;
- }
- if lastLine != pos.Line {
- v.Push(rawError{pos, msg});
- }
-}
-
-
// A single error in the parsed file.
type parseError struct {
src []byte; // source before error
return nil, &parseErrors{path, errs, nil};
}
- var raw rawErrorVector;
- prog, ok := parser.Parse(src, &raw, mode);
- if !ok {
+ prog, err := parser.Parse(src, mode);
+ if err != nil {
// sort and convert error list
- sort.Sort(&raw);
- errs := make([]parseError, raw.Len() + 1); // +1 for final fragment of source
- offs := 0;
- for i := 0; i < raw.Len(); i++ {
- r := raw.At(i);
- // Should always be true, but check for robustness.
- if 0 <= r.pos.Offset && r.pos.Offset <= len(src) {
- errs[i].src = src[offs : r.pos.Offset];
- offs = r.pos.Offset;
+ if errors, ok := err.(parser.ErrorList); ok {
+ sort.Sort(errors);
+ errs := make([]parseError, len(errors) + 1); // +1 for final fragment of source
+ offs := 0;
+ for i, r := range errors {
+ // Should always be true, but check for robustness.
+ if 0 <= r.Pos.Offset && r.Pos.Offset <= len(src) {
+ errs[i].src = src[offs : r.Pos.Offset];
+ offs = r.Pos.Offset;
+ }
+ errs[i].line = r.Pos.Line;
+ errs[i].msg = r.Msg;
}
- errs[i].line = r.pos.Line;
- errs[i].msg = r.msg;
+ errs[len(errors)].src = src[offs : len(src)];
+ return nil, &parseErrors{path, errs, src};
+ } else {
+ // TODO should have some default handling here to be more robust
+ panic("unreachable");
}
- errs[raw.Len()].src = src[offs : len(src)];
- return nil, &parseErrors{path, errs, src};
}
return prog, nil;
package main
import (
+ "astprinter";
"flag";
"fmt";
+ "format";
"go/ast";
"go/parser";
"go/token";
"io";
"os";
+ "sort";
"tabwriter";
-
- "astprinter";
- "format";
)
}
-// TODO(gri) move this into parser as default handler
-type ErrorHandler struct {
- filename string;
- lastline int;
-}
-
-
-func (h *ErrorHandler) Error(pos token.Position, msg string) {
- // only report errors that are on a new line
- // in the hope to avoid most follow-up errors
- if pos.Line == h.lastline {
- return;
- }
- h.lastline = pos.Line;
-
- // report error
- fmt.Fprintf(os.Stderr, "%s:%d:", h.filename, pos.Line);
- if columns {
- fmt.Fprintf(os.Stderr, "%d:", pos.Column);
- }
- fmt.Fprintf(os.Stderr, " %s\n", msg);
-}
-
-
func isValidPos(w io.Writer, env, value interface{}, name string) bool {
- return value.(token.Position).Line > 0;
+ pos := value.(token.Position);
+ return pos.IsValid();
}
continue; // proceed with next file
}
- prog, ok := parser.Parse(src, &ErrorHandler{filename, 0}, mode);
- if !ok {
+ prog, err := parser.Parse(src, mode);
+ if err != nil {
+ if errors, ok := err.(parser.ErrorList); ok {
+ sort.Sort(errors);
+ for _, e := range errors {
+ fmt.Fprintf(os.Stderr, "%s:%v\n", filename, e);
+ }
+ } else {
+ fmt.Fprintf(os.Stderr, "%s: %v\n", filename, err);
+ }
exitcode = 1;
continue; // proceed with next file
}