Example #1
0
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
}
Example #2
0
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
}