1
0
mirror of https://github.com/golang/go synced 2024-11-25 04:47:57 -07:00

More steps towards tracking of identifier scopes.

- provide scope to parse functions; if non-nil, parser uses the scope
  to declare and lookup identifiers
- resolve forward references where possible

R=rsc
CC=golang-dev
https://golang.org/cl/194098
This commit is contained in:
Robert Griesemer 2010-01-27 09:44:28 -08:00
parent 1c369bd55f
commit f39dc9fff2
13 changed files with 103 additions and 70 deletions

View File

@ -60,7 +60,7 @@ type FuncType struct {
func openProg(name string, p *Prog) { func openProg(name string, p *Prog) {
var err os.Error var err os.Error
p.AST, err = parser.ParseFile(name, nil, parser.ParseComments) p.AST, err = parser.ParseFile(name, nil, nil, parser.ParseComments)
if err != nil { if err != nil {
if list, ok := err.(scanner.ErrorList); ok { if list, ok := err.(scanner.ErrorList); ok {
// If err is a scanner.ErrorList, its String will print just // If err is a scanner.ErrorList, its String will print just

View File

@ -62,7 +62,7 @@ func (p *Prog) loadDebugInfo() {
for _, c := range p.Crefs { for _, c := range p.Crefs {
// If we've already found this name as a define, it is not a Cref. // If we've already found this name as a define, it is not a Cref.
if val, ok := defines[c.Name]; ok { if val, ok := defines[c.Name]; ok {
_, err := parser.ParseExpr("", val) _, err := parser.ParseExpr("", val, nil)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "The value in C.%s does not parse as a Go expression; cannot use.\n", c.Name) fmt.Fprintf(os.Stderr, "The value in C.%s does not parse as a Go expression; cannot use.\n", c.Name)
os.Exit(2) os.Exit(2)

View File

@ -122,7 +122,7 @@ func isPkgDir(dir *os.Dir) bool {
func pkgName(filename string) string { func pkgName(filename string) string {
file, err := parser.ParseFile(filename, nil, parser.PackageClauseOnly) file, err := parser.ParseFile(filename, nil, nil, parser.PackageClauseOnly)
if err != nil || file == nil { if err != nil || file == nil {
return "" return ""
} }
@ -207,7 +207,7 @@ func newDirTree(path, name string, depth, maxDepth int) *Directory {
nfiles++ nfiles++
if text == "" { if text == "" {
// no package documentation yet; take the first found // no package documentation yet; take the first found
file, err := parser.ParseFile(pathutil.Join(path, d.Name), nil, file, err := parser.ParseFile(pathutil.Join(path, d.Name), nil, nil,
parser.ParseComments|parser.PackageClauseOnly) parser.ParseComments|parser.PackageClauseOnly)
if err == nil && if err == nil &&
// Also accept fakePkgName, so we get synopses for commmands. // Also accept fakePkgName, so we get synopses for commmands.
@ -845,7 +845,7 @@ func serveGoSource(c *http.Conn, r *http.Request, path string) {
Error string Error string
} }
file, err := parser.ParseFile(path, nil, parser.ParseComments) file, err := parser.ParseFile(path, nil, nil, parser.ParseComments)
info.Source = StyledNode{file, &Styler{linetags: true, highlight: r.FormValue("h")}} info.Source = StyledNode{file, &Styler{linetags: true, highlight: r.FormValue("h")}}
if err != nil { if err != nil {
info.Error = err.String() info.Error = err.String()

View File

@ -600,7 +600,7 @@ func (x *Indexer) VisitFile(path string, d *os.Dir) {
return return
} }
file, err := parser.ParseFile(path, nil, parser.ParseComments) file, err := parser.ParseFile(path, nil, nil, parser.ParseComments)
if err != nil { if err != nil {
return // ignore files with (parse) errors return // ignore files with (parse) errors
} }

View File

@ -27,8 +27,8 @@ var (
rewriteRule = flag.String("r", "", "rewrite rule (e.g., 'α[β:len(α)] -> α[β:]')") rewriteRule = flag.String("r", "", "rewrite rule (e.g., 'α[β:len(α)] -> α[β:]')")
// debugging support // debugging support
checks = flag.Bool("checks", false, "do semantic checks")
comments = flag.Bool("comments", true, "print comments") comments = flag.Bool("comments", true, "print comments")
debug = flag.Bool("debug", false, "print debugging information")
trace = flag.Bool("trace", false, "print parse trace") trace = flag.Bool("trace", false, "print parse trace")
// layout control // layout control
@ -64,9 +64,6 @@ func usage() {
func initParserMode() { func initParserMode() {
parserMode = uint(0) parserMode = uint(0)
if *checks {
parserMode |= parser.CheckSemantics
}
if *comments { if *comments {
parserMode |= parser.ParseComments parserMode |= parser.ParseComments
} }
@ -103,7 +100,11 @@ func processFile(f *os.File) os.Error {
if *useOldParser { if *useOldParser {
file, err = oldParser.ParseFile(f.Name(), src, parserMode) file, err = oldParser.ParseFile(f.Name(), src, parserMode)
} else { } else {
file, err = parser.ParseFile(f.Name(), src, parserMode) var scope *ast.Scope
if *debug {
scope = ast.NewScope(nil)
}
file, err = parser.ParseFile(f.Name(), src, scope, parserMode)
} }
if err != nil { if err != nil {
return err return err

View File

@ -37,7 +37,7 @@ func initRewrite() {
// but there are problems with preserving formatting and also // but there are problems with preserving formatting and also
// with what a wildcard for a statement looks like. // with what a wildcard for a statement looks like.
func parseExpr(s string, what string) ast.Expr { func parseExpr(s string, what string) ast.Expr {
x, err := parser.ParseExpr("input", s) x, err := parser.ParseExpr("input", s, nil)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "parsing %s %s: %s\n", what, s, err) fmt.Fprintf(os.Stderr, "parsing %s %s: %s\n", what, s, err)
os.Exit(2) os.Exit(2)

View File

@ -198,5 +198,5 @@ func ParsePackage(path string, filter func(*os.Dir) bool, mode uint) (*ast.Packa
return nil, os.NewError(path + ": no package found") return nil, os.NewError(path + ": no package found")
} }
return &ast.Package{name, path, files}, nil return &ast.Package{name, path, nil, files}, nil
} }

View File

@ -722,5 +722,6 @@ type File struct {
type Package struct { type Package struct {
Name string // package name Name string // package name
Path string // package path Path string // package path
Scope *Scope // package scope
Files map[string]*File // path-relative filenames Files map[string]*File // path-relative filenames
} }

View File

@ -26,12 +26,11 @@ type Object struct {
Kind ObjKind Kind ObjKind
Pos token.Position // declaration position Pos token.Position // declaration position
Name string // declared name Name string // declared name
Scope *Scope // scope in which the Object is declared
} }
func NewObj(kind ObjKind, pos token.Position, name string) *Object { func NewObj(kind ObjKind, pos token.Position, name string) *Object {
return &Object{kind, pos, name, nil} return &Object{kind, pos, name}
} }
@ -55,16 +54,16 @@ func NewScope(outer *Scope) *Scope { return &Scope{outer, make(map[string]*Objec
// Declare attempts to insert a named object into the scope s. // Declare attempts to insert a named object into the scope s.
// If the scope does not contain an object with that name yet, // If the scope does not contain an object with that name yet,
// Declare inserts the object, and the result is true. Otherwise, // Declare inserts the object, and returns it. Otherwise, the
// the scope remains unchanged and the result is false. // scope remains unchanged and Declare returns the object found
func (s *Scope) Declare(obj *Object) bool { // in the scope instead.
if obj.Name != "_" { func (s *Scope) Declare(obj *Object) *Object {
if _, found := s.Objects[obj.Name]; found { decl, found := s.Objects[obj.Name]
return false if !found {
}
s.Objects[obj.Name] = obj s.Objects[obj.Name] = obj
decl = obj
} }
return true return decl
} }

View File

@ -10,6 +10,7 @@ import (
"bytes" "bytes"
"go/ast" "go/ast"
"go/scanner" "go/scanner"
"go/token"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
@ -50,62 +51,60 @@ func readSource(filename string, src interface{}) ([]byte, os.Error) {
} }
// TODO(gri) Simplify parser interface by splitting these functions func (p *parser) parseEOF() os.Error {
// into two parts: a single Init and a respective xParse p.expect(token.EOF)
// function. The Init function can be shared. return p.GetError(scanner.Sorted)
// }
// - the Init function will take a scope
// - if a scope is provided, the parser tracks declarations, otherwise it won't
// ParseExpr parses a Go expression and returns the corresponding // ParseExpr parses a Go expression and returns the corresponding
// AST node. The filename and src arguments have the same interpretation // AST node. The filename, src, and scope arguments have the same interpretation
// as for ParseFile. If there is an error, the result expression // as for ParseFile. If there is an error, the result expression
// may be nil or contain a partial AST. // may be nil or contain a partial AST.
// //
func ParseExpr(filename string, src interface{}) (ast.Expr, os.Error) { func ParseExpr(filename string, src interface{}, scope *ast.Scope) (ast.Expr, os.Error) {
data, err := readSource(filename, src) data, err := readSource(filename, src)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var p parser var p parser
p.init(filename, data, nil, 0) p.init(filename, data, scope, 0)
return p.parseExpr(), p.GetError(scanner.Sorted) return p.parseExpr(), p.parseEOF()
} }
// ParseStmtList parses a list of Go statements and returns the list // ParseStmtList parses a list of Go statements and returns the list
// of corresponding AST nodes. The filename and src arguments have the same // of corresponding AST nodes. The filename, src, and scope arguments have the same
// interpretation as for ParseFile. If there is an error, the node // interpretation as for ParseFile. If there is an error, the node
// list may be nil or contain partial ASTs. // list may be nil or contain partial ASTs.
// //
func ParseStmtList(filename string, src interface{}) ([]ast.Stmt, os.Error) { func ParseStmtList(filename string, src interface{}, scope *ast.Scope) ([]ast.Stmt, os.Error) {
data, err := readSource(filename, src) data, err := readSource(filename, src)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var p parser var p parser
p.init(filename, data, nil, 0) p.init(filename, data, scope, 0)
return p.parseStmtList(), p.GetError(scanner.Sorted) return p.parseStmtList(), p.parseEOF()
} }
// ParseDeclList parses a list of Go declarations and returns the list // ParseDeclList parses a list of Go declarations and returns the list
// of corresponding AST nodes. The filename and src arguments have the same // of corresponding AST nodes. The filename, src, and scope arguments have the same
// interpretation as for ParseFile. If there is an error, the node // interpretation as for ParseFile. If there is an error, the node
// list may be nil or contain partial ASTs. // list may be nil or contain partial ASTs.
// //
func ParseDeclList(filename string, src interface{}) ([]ast.Decl, os.Error) { func ParseDeclList(filename string, src interface{}, scope *ast.Scope) ([]ast.Decl, os.Error) {
data, err := readSource(filename, src) data, err := readSource(filename, src)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var p parser var p parser
p.init(filename, data, nil, 0) p.init(filename, data, scope, 0)
return p.parseDeclList(), p.GetError(scanner.Sorted) return p.parseDeclList(), p.parseEOF()
} }
@ -118,6 +117,11 @@ func ParseDeclList(filename string, src interface{}) ([]ast.Decl, os.Error) {
// //
// If src == nil, ParseFile parses the file specified by filename. // If src == nil, ParseFile parses the file specified by filename.
// //
// If scope != nil, it is the immediately surrounding scope for the file
// (the package scope) and it is used to lookup and declare identifiers.
// When parsing multiple files belonging to a package, the same scope should
// be provided to all files.
//
// The mode parameter controls the amount of source text parsed and other // The mode parameter controls the amount of source text parsed and other
// optional parser functionality. // optional parser functionality.
// //
@ -127,21 +131,15 @@ func ParseDeclList(filename string, src interface{}) ([]ast.Decl, os.Error) {
// representing the fragments of erroneous source code). Multiple errors // representing the fragments of erroneous source code). Multiple errors
// are returned via a scanner.ErrorList which is sorted by file position. // are returned via a scanner.ErrorList which is sorted by file position.
// //
func ParseFile(filename string, src interface{}, mode uint) (*ast.File, os.Error) { func ParseFile(filename string, src interface{}, scope *ast.Scope, mode uint) (*ast.File, os.Error) {
data, err := readSource(filename, src) data, err := readSource(filename, src)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var p parser var p parser
// TODO(gri) Remove CheckSemantics flag and code below once
// scope is provided via Init.
var scope *ast.Scope
if mode&CheckSemantics != 0 {
scope = ast.NewScope(nil)
}
p.init(filename, data, scope, mode) p.init(filename, data, scope, mode)
return p.parseFile(), p.GetError(scanner.NoMultiples) return p.parseFile(), p.GetError(scanner.NoMultiples) // parseFile() reads to EOF
} }
@ -166,18 +164,19 @@ func ParseDir(path string, filter func(*os.Dir) bool, mode uint) (map[string]*as
return nil, err return nil, err
} }
scope := ast.NewScope(nil)
pkgs := make(map[string]*ast.Package) pkgs := make(map[string]*ast.Package)
for i := 0; i < len(list); i++ { for i := 0; i < len(list); i++ {
entry := &list[i] entry := &list[i]
if filter == nil || filter(entry) { if filter == nil || filter(entry) {
src, err := ParseFile(pathutil.Join(path, entry.Name), nil, mode) src, err := ParseFile(pathutil.Join(path, entry.Name), nil, scope, mode)
if err != nil { if err != nil {
return pkgs, err return pkgs, err
} }
name := src.Name.Name() name := src.Name.Name()
pkg, found := pkgs[name] pkg, found := pkgs[name]
if !found { if !found {
pkg = &ast.Package{name, path, make(map[string]*ast.File)} pkg = &ast.Package{name, path, scope, make(map[string]*ast.File)}
pkgs[name] = pkg pkgs[name] = pkg
} }
pkg.Files[entry.Name] = src pkg.Files[entry.Name] = src

View File

@ -30,7 +30,6 @@ const (
PackageClauseOnly uint = 1 << iota // parsing stops after package clause PackageClauseOnly uint = 1 << iota // parsing stops after package clause
ImportsOnly // parsing stops after import declarations ImportsOnly // parsing stops after import declarations
ParseComments // parse comments and add them to AST ParseComments // parse comments and add them to AST
CheckSemantics // do semantic checks (only declarations for now)
Trace // print a trace of parsed productions Trace // print a trace of parsed productions
) )
@ -322,9 +321,15 @@ func (p *parser) parseIdentList(kind ast.ObjKind) []*ast.Ident {
func (p *parser) declIdent(scope *ast.Scope, id *ast.Ident) { func (p *parser) declIdent(scope *ast.Scope, id *ast.Ident) {
ok := scope.Declare(id.Obj) decl := scope.Declare(id.Obj)
if p.checkDecl && !ok { if p.checkDecl && decl != id.Obj {
p.Error(id.Pos(), "'"+id.Name()+"' declared already") if decl.Kind == ast.Err {
// declared object is a forward declaration - update it
*decl = *id.Obj
id.Obj = decl
return
}
p.Error(id.Pos(), "'"+id.Name()+"' declared already at "+decl.Pos.String())
} }
} }
@ -355,9 +360,36 @@ func (p *parser) findIdent() *ast.Ident {
p.expect(token.IDENT) // use expect() error handling p.expect(token.IDENT) // use expect() error handling
} }
if obj == nil { if obj == nil {
// TODO(gri) These identifiers need to be tracked as // No declaration found: either we are outside any function
// unresolved identifiers in the package // (p.funcScope == nil) or the identifier is not declared
// scope so that they can be resolved later. // in any function. Try the file and package scope.
obj = p.fileScope.Lookup(name) // file scope is nested in package scope
if obj == nil {
// No declaration found anywhere: track as
// unresolved identifier in the package scope.
obj = ast.NewObj(ast.Err, pos, name)
p.pkgScope.Declare(obj)
}
}
return &ast.Ident{pos, obj}
}
func (p *parser) findIdentInScope(scope *ast.Scope) *ast.Ident {
pos := p.pos
name := "_"
var obj *ast.Object
if p.tok == token.IDENT {
name = string(p.lit)
obj = scope.Lookup(name)
p.next()
} else {
p.expect(token.IDENT) // use expect() error handling
}
if obj == nil {
// TODO(gri) At the moment we always arrive here because
// we don't track the lookup scope (and sometimes
// we can't). Just create a useable ident for now.
obj = ast.NewObj(ast.Err, pos, name) obj = ast.NewObj(ast.Err, pos, name)
} }
return &ast.Ident{pos, obj} return &ast.Ident{pos, obj}
@ -421,7 +453,7 @@ func (p *parser) parseQualifiedIdent() ast.Expr {
if p.tok == token.PERIOD { if p.tok == token.PERIOD {
// first identifier is a package identifier // first identifier is a package identifier
p.next() p.next()
sel := p.findIdent() sel := p.findIdentInScope(nil)
x = &ast.SelectorExpr{x, sel} x = &ast.SelectorExpr{x, sel}
} }
return x return x
@ -970,7 +1002,7 @@ func (p *parser) parseSelectorOrTypeAssertion(x ast.Expr) ast.Expr {
p.expect(token.PERIOD) p.expect(token.PERIOD)
if p.tok == token.IDENT { if p.tok == token.IDENT {
// selector // selector
sel := p.findIdent() sel := p.findIdentInScope(nil)
return &ast.SelectorExpr{x, sel} return &ast.SelectorExpr{x, sel}
} }
@ -1403,7 +1435,7 @@ func (p *parser) parseBranchStmt(tok token.Token) *ast.BranchStmt {
s := &ast.BranchStmt{p.pos, tok, nil} s := &ast.BranchStmt{p.pos, tok, nil}
p.expect(tok) p.expect(tok)
if tok != token.FALLTHROUGH && p.tok == token.IDENT { if tok != token.FALLTHROUGH && p.tok == token.IDENT {
s.Label = p.findIdent() s.Label = p.findIdentInScope(nil)
} }
p.expectSemi() p.expectSemi()
@ -1943,7 +1975,7 @@ func (p *parser) parseReceiver(scope *ast.Scope) *ast.Field {
} }
func (p *parser) parseFunctionDecl() *ast.FuncDecl { func (p *parser) parseFuncDecl() *ast.FuncDecl {
if p.trace { if p.trace {
defer un(trace(p, "FunctionDecl")) defer un(trace(p, "FunctionDecl"))
} }
@ -1988,7 +2020,7 @@ func (p *parser) parseDecl() ast.Decl {
f = parseVarSpec f = parseVarSpec
case token.FUNC: case token.FUNC:
return p.parseFunctionDecl() return p.parseFuncDecl()
default: default:
pos := p.pos pos := p.pos

View File

@ -5,6 +5,7 @@
package parser package parser
import ( import (
"go/ast"
"os" "os"
"testing" "testing"
) )
@ -20,7 +21,7 @@ var illegalInputs = []interface{}{
func TestParseIllegalInputs(t *testing.T) { func TestParseIllegalInputs(t *testing.T) {
for _, src := range illegalInputs { for _, src := range illegalInputs {
_, err := ParseFile("", src, 0) _, err := ParseFile("", src, nil, 0)
if err == nil { if err == nil {
t.Errorf("ParseFile(%v) should have failed", src) t.Errorf("ParseFile(%v) should have failed", src)
} }
@ -40,7 +41,7 @@ var validPrograms = []interface{}{
func TestParseValidPrograms(t *testing.T) { func TestParseValidPrograms(t *testing.T) {
for _, src := range validPrograms { for _, src := range validPrograms {
_, err := ParseFile("", src, 0) _, err := ParseFile("", src, ast.NewScope(nil), 0)
if err != nil { if err != nil {
t.Errorf("ParseFile(%q): %v", src, err) t.Errorf("ParseFile(%q): %v", src, err)
} }
@ -56,7 +57,7 @@ var validFiles = []string{
func TestParse3(t *testing.T) { func TestParse3(t *testing.T) {
for _, filename := range validFiles { for _, filename := range validFiles {
_, err := ParseFile(filename, nil, 0) _, err := ParseFile(filename, nil, ast.NewScope(nil), 0)
if err != nil { if err != nil {
t.Errorf("ParseFile(%s): %v", filename, err) t.Errorf("ParseFile(%s): %v", filename, err)
} }

View File

@ -51,7 +51,7 @@ func check(t *testing.T, source, golden string, mode checkMode) {
if mode&oldSyntax != 0 { if mode&oldSyntax != 0 {
prog, err = oldParser.ParseFile(source, nil, parser.ParseComments) prog, err = oldParser.ParseFile(source, nil, parser.ParseComments)
} else { } else {
prog, err = parser.ParseFile(source, nil, parser.ParseComments) prog, err = parser.ParseFile(source, nil, nil, parser.ParseComments)
} }
if err != nil { if err != nil {
t.Error(err) t.Error(err)