func (t *Translator) TranslatePackage(importPath string, files []*ast.File, fileSet *token.FileSet, importPkg func(string) (*Archive, error)) (*Archive, error) { 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), Selections: make(map[*ast.SelectorExpr]*types.Selection), } var errList ErrorList var previousErr error config := &types.Config{ Packages: t.typesPackages, Import: func(_ map[string]*types.Package, path string) (*types.Package, error) { if _, err := importPkg(path); err != nil { return nil, err } return t.typesPackages[path], nil }, Sizes: sizes32, Error: func(err error) { if previousErr != nil && previousErr.Error() == err.Error() { return } errList = append(errList, err) previousErr = err }, } typesPkg, err := config.Check(importPath, fileSet, files, info) if errList != nil { return nil, errList } if err != nil { return nil, err } t.typesPackages[importPath] = typesPkg c := &funcContext{ p: &pkgContext{ pkg: typesPkg, info: info, pkgVars: make(map[string]string), objectVars: make(map[types.Object]string), indentation: 1, dependencies: make(map[types.Object]bool), }, allVars: make(map[string]int), flowDatas: map[string]*flowData{"": &flowData{}}, caseCounter: 1, labelCases: make(map[string]int), hasGoto: make(map[ast.Node]bool), } for name := range reservedKeywords { c.allVars[name] = 1 } var functions []*ast.FuncDecl var initStmts []ast.Stmt var toplevelTypes []*types.TypeName var vars []*types.Var for _, file := range files { for _, decl := range file.Decls { switch d := decl.(type) { case *ast.FuncDecl: sig := c.p.info.Defs[d.Name].(*types.Func).Type().(*types.Signature) var recvType types.Type if sig.Recv() != nil { recvType = sig.Recv().Type() if ptr, isPtr := recvType.(*types.Pointer); isPtr { recvType = ptr.Elem() } } if isBlank(d.Name) { continue } if sig.Recv() == nil && d.Name.Name == "init" { initStmts = append(initStmts, d.Body.List...) continue } functions = append(functions, d) if sig.Recv() == nil { c.objectName(c.p.info.Defs[d.Name]) // register toplevel name } case *ast.GenDecl: switch d.Tok { case token.TYPE: for _, spec := range d.Specs { o := c.p.info.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName) toplevelTypes = append(toplevelTypes, o) c.objectName(o) // register toplevel name } case token.VAR: for _, spec := range d.Specs { for _, name := range spec.(*ast.ValueSpec).Names { if !isBlank(name) { o := c.p.info.Defs[name].(*types.Var) vars = append(vars, o) c.objectName(o) // register toplevel name } } } case token.CONST: // skip, constants are inlined } } } } collectDependencies := func(self types.Object, f func()) []DepId { c.p.dependencies = make(map[types.Object]bool) f() var deps []string for dep := range c.p.dependencies { if dep != self { deps = append(deps, dep.Pkg().Path()+":"+dep.Name()) } } sort.Strings(deps) depIds := make([]DepId, len(deps)) for i, dep := range deps { depIds[i] = DepId(dep) } return depIds } gcData := bytes.NewBuffer(nil) gcexporter.Write(typesPkg, gcData, sizes32) encodedFileSet := bytes.NewBuffer(nil) if err := fileSet.Write(json.NewEncoder(encodedFileSet).Encode); err != nil { return nil, err } archive := &Archive{ ImportPath: importPath, GcData: gcData.Bytes(), Dependencies: []string{"runtime"}, // all packages depend on runtime FileSet: encodedFileSet.Bytes(), } // imports for _, importedPkg := range typesPkg.Imports() { varName := c.newVariable(importedPkg.Name()) c.p.pkgVars[importedPkg.Path()] = varName archive.Imports = append(archive.Imports, Import{Path: importedPkg.Path(), VarName: varName}) } // types for _, o := range toplevelTypes { typeName := c.objectName(o) var d Decl d.Var = typeName d.DceFilters = []DepId{DepId(o.Name())} d.DceDeps = collectDependencies(o, func() { d.BodyCode = c.CatchOutput(0, func() { c.translateType(o, true) }) d.InitCode = c.CatchOutput(1, func() { c.initType(o) }) }) archive.Declarations = append(archive.Declarations, d) } // functions nativesOrig := pkgNatives[importPath] natives := make(map[string]string, len(nativesOrig)) for k, v := range nativesOrig { natives[k] = v } for _, fun := range functions { var d Decl o := c.p.info.Defs[fun.Name].(*types.Func) funName := fun.Name.Name if fun.Recv == nil { d.Var = c.objectName(o) if o.Name() != "main" { d.DceFilters = []DepId{DepId(o.Name())} } } if fun.Recv != nil { recvType := o.Type().(*types.Signature).Recv().Type() ptr, isPointer := recvType.(*types.Pointer) namedRecvType, _ := recvType.(*types.Named) if isPointer { namedRecvType = ptr.Elem().(*types.Named) } funName = namedRecvType.Obj().Name() + "." + funName d.DceFilters = []DepId{DepId(namedRecvType.Obj().Name())} if !fun.Name.IsExported() { d.DceFilters = append(d.DceFilters, DepId(fun.Name.Name)) } } native := natives[funName] delete(natives, funName) d.DceDeps = collectDependencies(o, func() { d.BodyCode = c.translateToplevelFunction(fun, native) }) archive.Declarations = append(archive.Declarations, d) if strings.HasPrefix(fun.Name.String(), "Test") { archive.Tests = append(archive.Tests, fun.Name.String()) } } // variables initOrder := c.p.info.InitOrder // workaround for https://code.google.com/p/go/issues/detail?id=6703#c6 if importPath == "math/rand" { findInit := func(name string) int { for i, init := range initOrder { if init.Lhs[0].Name() == name { return i } } panic("init not found") } i := findInit("rng_cooked") j := findInit("globalRand") if i > j { initOrder[i], initOrder[j] = initOrder[j], initOrder[i] } } varsWithInit := make(map[*types.Var]bool) for _, init := range initOrder { for _, o := range init.Lhs { varsWithInit[o] = true } } for _, o := range vars { var d Decl if !o.Exported() { d.Var = c.objectName(o) } if _, ok := varsWithInit[o]; !ok { d.DceDeps = collectDependencies(nil, func() { value := c.zeroValue(o.Type()) if native, ok := natives[o.Name()]; ok { value = native delete(natives, o.Name()) } d.InitCode = []byte(fmt.Sprintf("\t\t%s = %s;\n", c.objectName(o), value)) }) } d.DceFilters = []DepId{DepId(o.Name())} archive.Declarations = append(archive.Declarations, d) } for _, init := range initOrder { lhs := make([]ast.Expr, len(init.Lhs)) for i, o := range init.Lhs { ident := ast.NewIdent(o.Name()) c.p.info.Types[ident] = types.TypeAndValue{Type: o.Type()} c.p.info.Defs[ident] = o lhs[i] = ident varsWithInit[o] = true } var d Decl d.DceDeps = collectDependencies(nil, func() { d.InitCode = c.translateFunctionBody(1, []ast.Stmt{ &ast.AssignStmt{ Lhs: lhs, Tok: token.DEFINE, Rhs: []ast.Expr{init.Rhs}, }, }) }) if len(init.Lhs) == 1 { v := hasCallVisitor{c.p.info, false} ast.Walk(&v, init.Rhs) if !v.hasCall { d.DceFilters = []DepId{DepId(init.Lhs[0].Name())} } } archive.Declarations = append(archive.Declarations, d) } // natives var toplevel Decl toplevel.BodyCode = []byte(natives["toplevel"]) delete(natives, "toplevel") if toplevelDependencies, ok := natives["toplevelDependencies"]; ok { for _, dep := range strings.Split(toplevelDependencies, " ") { toplevel.DceDeps = append(toplevel.DceDeps, DepId(dep)) } delete(natives, "toplevelDependencies") } archive.Declarations = append(archive.Declarations, toplevel) // init functions var init Decl init.DceDeps = collectDependencies(nil, func() { init.InitCode = c.translateFunctionBody(1, initStmts) }) archive.Declarations = append(archive.Declarations, init) if len(natives) != 0 { panic("not all natives used: " + importPath) } var importedPaths []string for _, imp := range typesPkg.Imports() { importedPaths = append(importedPaths, imp.Path()) } sort.Strings(importedPaths) for _, impPath := range importedPaths { impOutput, err := importPkg(impPath) if err != nil { return nil, err } archive.AddDependenciesOf(impOutput) } archive.AddDependency(importPath) return archive, nil }
func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, importContext *ImportContext, minify bool) (*Archive, error) { 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), Selections: make(map[*ast.SelectorExpr]*types.Selection), } var errList ErrorList var previousErr error config := &types.Config{ Packages: importContext.Packages, Import: func(_ map[string]*types.Package, path string) (*types.Package, error) { if _, err := importContext.Import(path); err != nil { return nil, err } return importContext.Packages[path], nil }, Sizes: sizes32, Error: func(err error) { if previousErr != nil && previousErr.Error() == err.Error() { return } errList = append(errList, err) previousErr = err }, } typesPkg, err := config.Check(importPath, fileSet, files, info) if errList != nil { return nil, errList } if err != nil { return nil, err } importContext.Packages[importPath] = typesPkg gcData := bytes.NewBuffer(nil) gcexporter.Write(typesPkg, gcData, sizes32) encodedFileSet := bytes.NewBuffer(nil) if err := fileSet.Write(json.NewEncoder(encodedFileSet).Encode); err != nil { return nil, err } archive := &Archive{ ImportPath: PkgPath(importPath), GcData: gcData.Bytes(), Dependencies: []PkgPath{PkgPath("github.com/gopherjs/gopherjs/js"), PkgPath("runtime")}, // all packages depend on those FileSet: encodedFileSet.Bytes(), Minified: minify, } c := &funcContext{ p: &pkgContext{ pkg: typesPkg, info: info, importContext: importContext, comments: make(ast.CommentMap), funcContexts: make(map[*types.Func]*funcContext), pkgVars: make(map[string]string), objectVars: make(map[types.Object]string), escapingVars: make(map[types.Object]bool), indentation: 1, dependencies: make(map[types.Object]bool), minify: minify, }, allVars: make(map[string]int), flowDatas: map[string]*flowData{"": &flowData{}}, flattened: make(map[ast.Node]bool), blocking: make(map[ast.Node]bool), caseCounter: 1, labelCases: make(map[string]int), localCalls: make(map[*types.Func][][]ast.Node), } for name := range reservedKeywords { c.allVars[name] = 1 } // imports var importedPaths []string for _, importedPkg := range typesPkg.Imports() { varName := c.newVariableWithLevel(importedPkg.Name(), true, "") c.p.pkgVars[importedPkg.Path()] = varName archive.Imports = append(archive.Imports, PkgImport{Path: PkgPath(importedPkg.Path()), VarName: varName}) importedPaths = append(importedPaths, importedPkg.Path()) } sort.Strings(importedPaths) for _, impPath := range importedPaths { impOutput, err := importContext.Import(impPath) if err != nil { return nil, err } archive.AddDependenciesOf(impOutput) } var functions []*ast.FuncDecl var toplevelTypes []*types.TypeName var vars []*types.Var for _, file := range files { for k, v := range ast.NewCommentMap(fileSet, file, file.Comments) { c.p.comments[k] = v } for _, decl := range file.Decls { switch d := decl.(type) { case *ast.FuncDecl: sig := c.p.info.Defs[d.Name].(*types.Func).Type().(*types.Signature) var recvType types.Type if sig.Recv() != nil { recvType = sig.Recv().Type() if ptr, isPtr := recvType.(*types.Pointer); isPtr { recvType = ptr.Elem() } } o := c.p.info.Defs[d.Name].(*types.Func) c.p.funcContexts[o] = c.p.analyzeFunction(sig, d.Body) if sig.Recv() == nil { c.objectName(o) // register toplevel name } if !isBlank(d.Name) { functions = append(functions, d) } case *ast.GenDecl: switch d.Tok { case token.TYPE: for _, spec := range d.Specs { o := c.p.info.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName) toplevelTypes = append(toplevelTypes, o) c.objectName(o) // register toplevel name } case token.VAR: for _, spec := range d.Specs { for _, name := range spec.(*ast.ValueSpec).Names { if !isBlank(name) { o := c.p.info.Defs[name].(*types.Var) vars = append(vars, o) c.objectName(o) // register toplevel name } } } case token.CONST: // skip, constants are inlined } } } } for { done := true for _, context := range c.p.funcContexts { for obj, calls := range context.localCalls { if len(c.p.funcContexts[obj].blocking) != 0 { for _, call := range calls { context.markBlocking(call) } delete(context.localCalls, obj) done = false } } } if done { break } } collectDependencies := func(self types.Object, f func()) []DepId { c.p.dependencies = make(map[types.Object]bool) f() var deps []string for dep := range c.p.dependencies { if dep != self { deps = append(deps, dep.Pkg().Path()+":"+dep.Name()) } } sort.Strings(deps) depIds := make([]DepId, len(deps)) for i, dep := range deps { depIds[i] = DepId(dep) } return depIds } // types for _, o := range toplevelTypes { typeName := c.objectName(o) var d Decl d.Vars = []string{typeName} d.DceFilters = []DepId{DepId(o.Name())} d.DceDeps = collectDependencies(o, func() { d.BodyCode = removeWhitespace(c.CatchOutput(0, func() { c.translateType(o, true) }), minify) d.InitCode = removeWhitespace(c.CatchOutput(1, func() { c.initType(o) }), minify) }) archive.Declarations = append(archive.Declarations, d) } // variables varsWithInit := make(map[*types.Var]bool) for _, init := range c.p.info.InitOrder { for _, o := range init.Lhs { varsWithInit[o] = true } } for _, o := range vars { var d Decl if !o.Exported() { d.Vars = []string{c.objectName(o)} } if _, ok := varsWithInit[o]; !ok { d.DceDeps = collectDependencies(nil, func() { value := c.zeroValue(o.Type()) if importPath == "runtime" && o.Name() == "sizeof_C_MStats" { value = "3712" } d.InitCode = removeWhitespace([]byte(fmt.Sprintf("\t\t%s = %s;\n", c.objectName(o), value)), minify) }) } d.DceFilters = []DepId{DepId(o.Name())} archive.Declarations = append(archive.Declarations, d) } for _, init := range c.p.info.InitOrder { lhs := make([]ast.Expr, len(init.Lhs)) for i, o := range init.Lhs { ident := ast.NewIdent(o.Name()) c.p.info.Defs[ident] = o lhs[i] = c.setType(ident, o.Type()) varsWithInit[o] = true } var d Decl d.DceDeps = collectDependencies(nil, func() { c.localVars = nil d.InitCode = removeWhitespace(c.CatchOutput(1, func() { ast.Walk(c, init.Rhs) c.translateStmt(&ast.AssignStmt{ Lhs: lhs, Tok: token.DEFINE, Rhs: []ast.Expr{init.Rhs}, }, "") }), minify) d.Vars = append(d.Vars, c.localVars...) }) if len(init.Lhs) == 1 { v := hasCallVisitor{c.p.info, false} ast.Walk(&v, init.Rhs) if !v.hasCall { d.DceFilters = []DepId{DepId(init.Lhs[0].Name())} } } archive.Declarations = append(archive.Declarations, d) } // functions var mainFunc *types.Func for _, fun := range functions { o := c.p.info.Defs[fun.Name].(*types.Func) context := c.p.funcContexts[o] d := Decl{ FullName: []byte(o.FullName()), Blocking: len(context.blocking) != 0, } if fun.Recv == nil { d.Vars = []string{c.objectName(o)} switch o.Name() { case "main": mainFunc = o case "init": d.InitCode = removeWhitespace(c.CatchOutput(1, func() { id := c.newIdent("", types.NewSignature(nil, nil, nil, nil, false)) c.p.info.Uses[id] = o call := &ast.CallExpr{Fun: id} c.Visit(call) c.translateStmt(&ast.ExprStmt{X: call}, "") }), minify) default: d.DceFilters = []DepId{DepId(o.Name())} } } if fun.Recv != nil { recvType := o.Type().(*types.Signature).Recv().Type() ptr, isPointer := recvType.(*types.Pointer) namedRecvType, _ := recvType.(*types.Named) if isPointer { namedRecvType = ptr.Elem().(*types.Named) } d.DceFilters = []DepId{DepId(namedRecvType.Obj().Name())} if !fun.Name.IsExported() { d.DceFilters = append(d.DceFilters, DepId(fun.Name.Name)) } } d.DceDeps = collectDependencies(o, func() { d.BodyCode = removeWhitespace(c.translateToplevelFunction(fun, context), minify) }) archive.Declarations = append(archive.Declarations, d) if fun.Recv == nil && strings.HasPrefix(fun.Name.String(), "Test") { archive.Tests = append(archive.Tests, fun.Name.String()) } } archive.BlockingInit = len(c.blocking) != 0 // $run function if typesPkg.Name() == "main" { var stmts []ast.Stmt for _, dep := range archive.Dependencies { id := c.newIdent(fmt.Sprintf(`$packages["%s"].$init`, dep), types.NewSignature(nil, nil, nil, nil, false)) call := &ast.CallExpr{Fun: id} depArchive, err := importContext.Import(string(dep)) if err != nil { panic(err) } if depArchive.BlockingInit { c.blocking[call] = true c.flattened[call] = true } stmts = append(stmts, &ast.ExprStmt{X: call}) } { id := c.newIdent("$pkg.$init", types.NewSignature(nil, nil, nil, nil, false)) call := &ast.CallExpr{Fun: id} if archive.BlockingInit { c.blocking[call] = true c.flattened[call] = true } stmts = append(stmts, &ast.ExprStmt{X: call}) } { id := c.newIdent("", types.NewSignature(nil, nil, nil, nil, false)) c.p.info.Uses[id] = mainFunc call := &ast.CallExpr{Fun: id} c.Visit(call) stmts = append(stmts, &ast.ExprStmt{X: call}) } archive.Declarations = append(archive.Declarations, Decl{ BodyCode: removeWhitespace(append(append([]byte("\t$pkg.$run = function($b) {\n"), c.translateFunctionBody(stmts)...), []byte("\t};\n")...), minify), }) } return archive, nil }