mirror of https://github.com/tinygo-org/tinygo.git
Ayke van Laethem
6 years ago
7 changed files with 416 additions and 20 deletions
@ -0,0 +1,27 @@ |
|||
package loader |
|||
|
|||
// Errors contains a list of parser errors or a list of typechecker errors for
|
|||
// the given package.
|
|||
type Errors struct { |
|||
Pkg *Package |
|||
Errs []error |
|||
} |
|||
|
|||
func (e Errors) Error() string { |
|||
return "could not compile: " + e.Errs[0].Error() |
|||
} |
|||
|
|||
// ImportCycleErrors is returned when encountering an import cycle. The list of
|
|||
// packages is a list from the root package to the leaf package that imports one
|
|||
// of the packages in the list.
|
|||
type ImportCycleError struct { |
|||
Packages []string |
|||
} |
|||
|
|||
func (e *ImportCycleError) Error() string { |
|||
msg := "import cycle: " + e.Packages[0] |
|||
for _, path := range e.Packages[1:] { |
|||
msg += " → " + path |
|||
} |
|||
return msg |
|||
} |
@ -0,0 +1,333 @@ |
|||
package loader |
|||
|
|||
import ( |
|||
"errors" |
|||
"go/ast" |
|||
"go/build" |
|||
"go/parser" |
|||
"go/token" |
|||
"go/types" |
|||
"os" |
|||
"path/filepath" |
|||
"sort" |
|||
) |
|||
|
|||
// Program holds all packages and some metadata about the program as a whole.
|
|||
type Program struct { |
|||
Build *build.Context |
|||
Packages map[string]*Package |
|||
sorted []*Package |
|||
fset *token.FileSet |
|||
TypeChecker types.Config |
|||
Dir string // current working directory (for error reporting)
|
|||
} |
|||
|
|||
// Package holds a loaded package, its imports, and its parsed files.
|
|||
type Package struct { |
|||
*Program |
|||
*build.Package |
|||
Imports map[string]*Package |
|||
Importing bool |
|||
Files []*ast.File |
|||
Pkg *types.Package |
|||
types.Info |
|||
} |
|||
|
|||
// Import loads the given package relative to srcDir (for the vendor directory).
|
|||
// It only loads the current package without recursion.
|
|||
func (p *Program) Import(path, srcDir string) (*Package, error) { |
|||
if p.Packages == nil { |
|||
p.Packages = make(map[string]*Package) |
|||
} |
|||
|
|||
// Load this package.
|
|||
buildPkg, err := p.Build.Import(path, srcDir, build.ImportComment) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
if existingPkg, ok := p.Packages[buildPkg.ImportPath]; ok { |
|||
// Already imported, or at least started the import.
|
|||
return existingPkg, nil |
|||
} |
|||
p.sorted = nil // invalidate the sorted order of packages
|
|||
pkg := p.newPackage(buildPkg) |
|||
p.Packages[buildPkg.ImportPath] = pkg |
|||
return pkg, nil |
|||
} |
|||
|
|||
// ImportFile loads and parses the import statements in the given path and
|
|||
// creates a pseudo-package out of it.
|
|||
func (p *Program) ImportFile(path string) (*Package, error) { |
|||
if p.Packages == nil { |
|||
p.Packages = make(map[string]*Package) |
|||
} |
|||
if _, ok := p.Packages[path]; ok { |
|||
// unlikely
|
|||
return nil, errors.New("loader: cannot import file that is already imported as package: " + path) |
|||
} |
|||
|
|||
file, err := p.parseFile(path, parser.ImportsOnly) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
buildPkg := &build.Package{ |
|||
Dir: filepath.Dir(path), |
|||
ImportPath: path, |
|||
GoFiles: []string{filepath.Base(path)}, |
|||
} |
|||
for _, importSpec := range file.Imports { |
|||
buildPkg.Imports = append(buildPkg.Imports, importSpec.Path.Value[1:len(importSpec.Path.Value)-1]) |
|||
} |
|||
p.sorted = nil // invalidate the sorted order of packages
|
|||
pkg := p.newPackage(buildPkg) |
|||
p.Packages[buildPkg.ImportPath] = pkg |
|||
return pkg, nil |
|||
} |
|||
|
|||
// newPackage instantiates a new *Package object with initialized members.
|
|||
func (p *Program) newPackage(pkg *build.Package) *Package { |
|||
return &Package{ |
|||
Program: p, |
|||
Package: pkg, |
|||
Imports: make(map[string]*Package, len(pkg.Imports)), |
|||
Info: types.Info{ |
|||
Types: make(map[ast.Expr]types.TypeAndValue), |
|||
Defs: make(map[*ast.Ident]types.Object), |
|||
Uses: make(map[*ast.Ident]types.Object), |
|||
Implicits: make(map[ast.Node]types.Object), |
|||
Scopes: make(map[ast.Node]*types.Scope), |
|||
Selections: make(map[*ast.SelectorExpr]*types.Selection), |
|||
}, |
|||
} |
|||
} |
|||
|
|||
// Sorted returns a list of all packages, sorted in a way that no packages come
|
|||
// before the packages they depend upon.
|
|||
func (p *Program) Sorted() []*Package { |
|||
if p.sorted == nil { |
|||
p.sort() |
|||
} |
|||
return p.sorted |
|||
} |
|||
|
|||
func (p *Program) sort() { |
|||
p.sorted = nil |
|||
packageList := make([]*Package, 0, len(p.Packages)) |
|||
packageSet := make(map[string]struct{}, len(p.Packages)) |
|||
worklist := make([]string, 0, len(p.Packages)) |
|||
for path := range p.Packages { |
|||
worklist = append(worklist, path) |
|||
} |
|||
sort.Strings(worklist) |
|||
for len(worklist) != 0 { |
|||
pkgPath := worklist[0] |
|||
pkg := p.Packages[pkgPath] |
|||
|
|||
if _, ok := packageSet[pkgPath]; ok { |
|||
// Package already in the final package list.
|
|||
worklist = worklist[1:] |
|||
continue |
|||
} |
|||
|
|||
unsatisfiedImports := make([]string, 0) |
|||
for _, pkg := range pkg.Imports { |
|||
if _, ok := packageSet[pkg.ImportPath]; ok { |
|||
continue |
|||
} |
|||
unsatisfiedImports = append(unsatisfiedImports, pkg.ImportPath) |
|||
} |
|||
sort.Strings(unsatisfiedImports) |
|||
if len(unsatisfiedImports) == 0 { |
|||
// All dependencies of this package are satisfied, so add this
|
|||
// package to the list.
|
|||
packageList = append(packageList, pkg) |
|||
packageSet[pkgPath] = struct{}{} |
|||
worklist = worklist[1:] |
|||
} else { |
|||
// Prepend all dependencies to the worklist and reconsider this
|
|||
// package (by not removing it from the worklist). At that point, it
|
|||
// must be possible to add it to packageList.
|
|||
worklist = append(unsatisfiedImports, worklist...) |
|||
} |
|||
} |
|||
|
|||
p.sorted = packageList |
|||
} |
|||
|
|||
// Parse recursively imports all packages, parses them, and typechecks them.
|
|||
//
|
|||
// The returned error may be an Errors error, which contains a list of errors.
|
|||
//
|
|||
// Idempotent.
|
|||
func (p *Program) Parse() error { |
|||
// Load all imports
|
|||
for _, pkg := range p.Sorted() { |
|||
err := pkg.importRecursively() |
|||
if err != nil { |
|||
if err, ok := err.(*ImportCycleError); ok { |
|||
err.Packages = append([]string{pkg.ImportPath}, err.Packages...) |
|||
} |
|||
return err |
|||
} |
|||
} |
|||
|
|||
// Parse all packages.
|
|||
for _, pkg := range p.Sorted() { |
|||
err := pkg.Parse() |
|||
if err != nil { |
|||
return err |
|||
} |
|||
} |
|||
|
|||
// Typecheck all packages.
|
|||
for _, pkg := range p.Sorted() { |
|||
err := pkg.Check() |
|||
if err != nil { |
|||
return err |
|||
} |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
// parseFile is a wrapper around parser.ParseFile.
|
|||
func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) { |
|||
if p.fset == nil { |
|||
p.fset = token.NewFileSet() |
|||
} |
|||
|
|||
rd, err := os.Open(path) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
defer rd.Close() |
|||
relpath := path |
|||
if filepath.IsAbs(path) { |
|||
relpath, err = filepath.Rel(p.Dir, path) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
} |
|||
return parser.ParseFile(p.fset, relpath, rd, mode) |
|||
} |
|||
|
|||
// Parse parses and typechecks this package.
|
|||
//
|
|||
// Idempotent.
|
|||
func (p *Package) Parse() error { |
|||
if len(p.Files) != 0 { |
|||
return nil |
|||
} |
|||
|
|||
// Load the AST.
|
|||
// TODO: do this in parallel.
|
|||
if p.ImportPath == "unsafe" { |
|||
// Special case for the unsafe package. Don't even bother loading
|
|||
// the files.
|
|||
p.Pkg = types.Unsafe |
|||
return nil |
|||
} |
|||
|
|||
files, err := p.parseFiles() |
|||
if err != nil { |
|||
return err |
|||
} |
|||
p.Files = files |
|||
|
|||
return nil |
|||
} |
|||
|
|||
// Check runs the package through the typechecker. The package must already be
|
|||
// loaded and all dependencies must have been checked already.
|
|||
//
|
|||
// Idempotent.
|
|||
func (p *Package) Check() error { |
|||
if p.Pkg != nil { |
|||
return nil |
|||
} |
|||
|
|||
var typeErrors []error |
|||
checker := p.TypeChecker |
|||
checker.Error = func(err error) { |
|||
typeErrors = append(typeErrors, err) |
|||
} |
|||
|
|||
// Do typechecking of the package.
|
|||
checker.Importer = p |
|||
|
|||
typesPkg, err := checker.Check(p.ImportPath, p.fset, p.Files, &p.Info) |
|||
if err != nil { |
|||
if err, ok := err.(Errors); ok { |
|||
return err |
|||
} |
|||
return Errors{p, typeErrors} |
|||
} |
|||
p.Pkg = typesPkg |
|||
return nil |
|||
} |
|||
|
|||
// parseFiles parses the loaded list of files and returns this list.
|
|||
func (p *Package) parseFiles() ([]*ast.File, error) { |
|||
if len(p.CgoFiles) != 0 { |
|||
return nil, errors.New("loader: todo cgo: " + p.CgoFiles[0]) |
|||
} |
|||
|
|||
// TODO: do this concurrently.
|
|||
var files []*ast.File |
|||
var fileErrs []error |
|||
for _, file := range p.GoFiles { |
|||
f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments) |
|||
if err != nil { |
|||
fileErrs = append(fileErrs, err) |
|||
} else { |
|||
files = append(files, f) |
|||
} |
|||
} |
|||
if len(fileErrs) != 0 { |
|||
return nil, Errors{p, fileErrs} |
|||
} |
|||
return files, nil |
|||
} |
|||
|
|||
// Import implements types.Importer. It loads and parses packages it encounters
|
|||
// along the way, if needed.
|
|||
func (p *Package) Import(to string) (*types.Package, error) { |
|||
if to == "unsafe" { |
|||
return types.Unsafe, nil |
|||
} |
|||
if _, ok := p.Imports[to]; ok { |
|||
return p.Imports[to].Pkg, nil |
|||
} else { |
|||
panic("package not imported: " + to) |
|||
} |
|||
} |
|||
|
|||
// importRecursively calls Program.Import() on all imported packages, and calls
|
|||
// importRecursively() on the imported packages as well.
|
|||
//
|
|||
// Idempotent.
|
|||
func (p *Package) importRecursively() error { |
|||
p.Importing = true |
|||
for _, to := range p.Package.Imports { |
|||
if _, ok := p.Imports[to]; ok { |
|||
continue |
|||
} |
|||
importedPkg, err := p.Program.Import(to, p.Package.Dir) |
|||
if err != nil { |
|||
if err, ok := err.(*ImportCycleError); ok { |
|||
err.Packages = append([]string{p.ImportPath}, err.Packages...) |
|||
} |
|||
return err |
|||
} |
|||
if importedPkg.Importing { |
|||
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}} |
|||
} |
|||
err = importedPkg.importRecursively() |
|||
if err != nil { |
|||
return err |
|||
} |
|||
p.Imports[to] = importedPkg |
|||
} |
|||
p.Importing = false |
|||
return nil |
|||
} |
@ -0,0 +1,18 @@ |
|||
package loader |
|||
|
|||
import ( |
|||
"golang.org/x/tools/go/ssa" |
|||
) |
|||
|
|||
// LoadSSA constructs the SSA form of the loaded packages.
|
|||
//
|
|||
// The program must already be parsed and type-checked with the .Parse() method.
|
|||
func (p *Program) LoadSSA() *ssa.Program { |
|||
prog := ssa.NewProgram(p.fset, ssa.SanityCheckFunctions|ssa.BareInits|ssa.GlobalDebug) |
|||
|
|||
for _, pkg := range p.Sorted() { |
|||
prog.CreatePackage(pkg.Pkg, pkg.Files, &pkg.Info, true) |
|||
} |
|||
|
|||
return prog |
|||
} |
Loading…
Reference in new issue