| // Copyright 2009 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // This file contains the exported entry points for invoking the parser. |
| |
| package parser |
| |
| import ( |
| "bytes" |
| "errors" |
| "golang.org/x/website/internal/backport/go/ast" |
| "golang.org/x/website/internal/backport/go/token" |
| "io" |
| "io/fs" |
| "os" |
| "path/filepath" |
| "strings" |
| ) |
| |
| // If src != nil, readSource converts src to a []byte if possible; |
| // otherwise it returns an error. If src == nil, readSource returns |
| // the result of reading the file specified by filename. |
| func readSource(filename string, src interface{}) ([]byte, error) { |
| if src != nil { |
| switch s := src.(type) { |
| case string: |
| return []byte(s), nil |
| case []byte: |
| return s, nil |
| case *bytes.Buffer: |
| // is io.Reader, but src is already available in []byte form |
| if s != nil { |
| return s.Bytes(), nil |
| } |
| case io.Reader: |
| return io.ReadAll(s) |
| } |
| return nil, errors.New("invalid source") |
| } |
| return os.ReadFile(filename) |
| } |
| |
| // A Mode value is a set of flags (or 0). |
| // They control the amount of source code parsed and other optional |
| // parser functionality. |
| type Mode uint |
| |
| const ( |
| PackageClauseOnly Mode = 1 << iota // stop parsing after package clause |
| ImportsOnly // stop parsing after import declarations |
| ParseComments // parse comments and add them to AST |
| Trace // print a trace of parsed productions |
| DeclarationErrors // report declaration errors |
| SpuriousErrors // same as AllErrors, for backward-compatibility |
| SkipObjectResolution // don't resolve identifiers to objects - see ParseFile |
| AllErrors = SpuriousErrors // report all errors (not just the first 10 on different lines) |
| ) |
| |
| // ParseFile parses the source code of a single Go source file and returns |
| // the corresponding ast.File node. The source code may be provided via |
| // the filename of the source file, or via the src parameter. |
| // |
| // If src != nil, ParseFile parses the source from src and the filename is |
| // only used when recording position information. The type of the argument |
| // for the src parameter must be string, []byte, or io.Reader. |
| // If src == nil, ParseFile parses the file specified by filename. |
| // |
| // The mode parameter controls the amount of source text parsed and other |
| // optional parser functionality. If the SkipObjectResolution mode bit is set, |
| // the object resolution phase of parsing will be skipped, causing File.Scope, |
| // File.Unresolved, and all Ident.Obj fields to be nil. |
| // |
| // Position information is recorded in the file set fset, which must not be |
| // nil. |
| // |
| // If the source couldn't be read, the returned AST 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.Bad* nodes |
| // representing the fragments of erroneous source code). Multiple errors |
| // are returned via a scanner.ErrorList which is sorted by source position. |
| func ParseFile(fset *token.FileSet, filename string, src interface{}, mode Mode) (f *ast.File, err error) { |
| if fset == nil { |
| panic("parser.ParseFile: no token.FileSet provided (fset == nil)") |
| } |
| |
| // get source |
| text, err := readSource(filename, src) |
| if err != nil { |
| return nil, err |
| } |
| |
| var p parser |
| defer func() { |
| if e := recover(); e != nil { |
| // resume same panic if it's not a bailout |
| if _, ok := e.(bailout); !ok { |
| panic(e) |
| } |
| } |
| |
| // set result values |
| if f == nil { |
| // source is not a valid Go source file - satisfy |
| // ParseFile API and return a valid (but) empty |
| // *ast.File |
| f = &ast.File{ |
| Name: new(ast.Ident), |
| Scope: ast.NewScope(nil), |
| } |
| } |
| |
| p.errors.Sort() |
| err = p.errors.Err() |
| }() |
| |
| // parse source |
| p.init(fset, filename, text, mode) |
| f = p.parseFile() |
| |
| return |
| } |
| |
| // ParseDir calls ParseFile for all files with names ending in ".go" in the |
| // directory specified by path and returns a map of package name -> package |
| // AST with all the packages found. |
| // |
| // If filter != nil, only the files with fs.FileInfo entries passing through |
| // the filter (and ending in ".go") are considered. The mode bits are passed |
| // to ParseFile unchanged. Position information is recorded in fset, which |
| // must not be nil. |
| // |
| // If the directory couldn't be read, a nil map and the respective error are |
| // returned. If a parse error occurred, a non-nil but incomplete map and the |
| // first error encountered are returned. |
| func ParseDir(fset *token.FileSet, path string, filter func(fs.FileInfo) bool, mode Mode) (pkgs map[string]*ast.Package, first error) { |
| list, err := os.ReadDir(path) |
| if err != nil { |
| return nil, err |
| } |
| |
| pkgs = make(map[string]*ast.Package) |
| for _, d := range list { |
| if d.IsDir() || !strings.HasSuffix(d.Name(), ".go") { |
| continue |
| } |
| if filter != nil { |
| info, err := d.Info() |
| if err != nil { |
| return nil, err |
| } |
| if !filter(info) { |
| continue |
| } |
| } |
| filename := filepath.Join(path, d.Name()) |
| if src, err := ParseFile(fset, filename, nil, mode); err == nil { |
| name := src.Name.Name |
| pkg, found := pkgs[name] |
| if !found { |
| pkg = &ast.Package{ |
| Name: name, |
| Files: make(map[string]*ast.File), |
| } |
| pkgs[name] = pkg |
| } |
| pkg.Files[filename] = src |
| } else if first == nil { |
| first = err |
| } |
| } |
| |
| return |
| } |
| |
| // ParseExprFrom is a convenience function for parsing an expression. |
| // The arguments have the same meaning as for ParseFile, but the source must |
| // be a valid Go (type or value) expression. Specifically, fset must not |
| // be nil. |
| // |
| // If the source couldn't be read, the returned AST 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.Bad* nodes |
| // representing the fragments of erroneous source code). Multiple errors |
| // are returned via a scanner.ErrorList which is sorted by source position. |
| func ParseExprFrom(fset *token.FileSet, filename string, src interface{}, mode Mode) (expr ast.Expr, err error) { |
| if fset == nil { |
| panic("parser.ParseExprFrom: no token.FileSet provided (fset == nil)") |
| } |
| |
| // get source |
| text, err := readSource(filename, src) |
| if err != nil { |
| return nil, err |
| } |
| |
| var p parser |
| defer func() { |
| if e := recover(); e != nil { |
| // resume same panic if it's not a bailout |
| if _, ok := e.(bailout); !ok { |
| panic(e) |
| } |
| } |
| p.errors.Sort() |
| err = p.errors.Err() |
| }() |
| |
| // parse expr |
| p.init(fset, filename, text, mode) |
| expr = p.parseRhsOrType() |
| |
| // If a semicolon was inserted, consume it; |
| // report an error if there's more tokens. |
| if p.tok == token.SEMICOLON && p.lit == "\n" { |
| p.next() |
| } |
| p.expect(token.EOF) |
| |
| return |
| } |
| |
| // ParseExpr is a convenience function for obtaining the AST of an expression x. |
| // The position information recorded in the AST is undefined. The filename used |
| // in error messages is the empty string. |
| // |
| // If syntax errors were found, the result is a partial AST (with ast.Bad* nodes |
| // representing the fragments of erroneous source code). Multiple errors are |
| // returned via a scanner.ErrorList which is sorted by source position. |
| func ParseExpr(x string) (ast.Expr, error) { |
| return ParseExprFrom(token.NewFileSet(), "", []byte(x), 0) |
| } |