func (c *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { c.SetPos(stmt.Pos()) stmt = filter.IncDecStmt(stmt, c.p.Info) stmt = filter.Assign(stmt, c.p.Info) switch s := stmt.(type) { case *ast.BlockStmt: c.translateStmtList(s.List) case *ast.IfStmt: if s.Init != nil { c.translateStmt(s.Init, nil) } var caseClauses []ast.Stmt ifStmt := s for { caseClauses = append(caseClauses, &ast.CaseClause{List: []ast.Expr{ifStmt.Cond}, Body: ifStmt.Body.List}) switch elseStmt := ifStmt.Else.(type) { case *ast.IfStmt: if elseStmt.Init != nil { caseClauses = append(caseClauses, &ast.CaseClause{List: nil, Body: []ast.Stmt{elseStmt}}) break } ifStmt = elseStmt continue case *ast.BlockStmt: caseClauses = append(caseClauses, &ast.CaseClause{List: nil, Body: elseStmt.List}) case *ast.EmptyStmt, nil: // no else clause default: panic(fmt.Sprintf("Unhandled else: %T\n", elseStmt)) } break } c.translateBranchingStmt(caseClauses, false, nil, nil, nil, c.Flattened[s]) case *ast.SwitchStmt: if s.Init != nil { c.translateStmt(s.Init, nil) } tag := s.Tag if tag == nil { tag = ast.NewIdent("true") c.p.Types[tag] = types.TypeAndValue{Type: types.Typ[types.Bool], Value: exact.MakeBool(true)} } if c.p.Types[tag].Value == nil { refVar := c.newVariable("_ref") c.Printf("%s = %s;", refVar, c.translateExpr(tag)) tag = c.newIdent(refVar, c.p.Types[tag].Type) } translateCond := func(cond ast.Expr) *expression { return c.translateExpr(&ast.BinaryExpr{ X: tag, Op: token.EQL, Y: cond, }) } c.translateBranchingStmt(s.Body.List, true, translateCond, nil, label, c.Flattened[s]) case *ast.TypeSwitchStmt: if s.Init != nil { c.translateStmt(s.Init, nil) } refVar := c.newVariable("_ref") var expr ast.Expr var printCaseBodyPrefix func(index int) switch a := s.Assign.(type) { case *ast.AssignStmt: expr = a.Rhs[0].(*ast.TypeAssertExpr).X printCaseBodyPrefix = func(index int) { value := refVar caseClause := s.Body.List[index].(*ast.CaseClause) if len(caseClause.List) == 1 { t := c.p.Types[caseClause.List[0]].Type if _, isInterface := t.Underlying().(*types.Interface); !isInterface && !types.Identical(t, types.Typ[types.UntypedNil]) { value += ".$val" } } c.Printf("%s = %s;", c.objectName(c.p.Implicits[caseClause]), value) } case *ast.ExprStmt: expr = a.X.(*ast.TypeAssertExpr).X } c.Printf("%s = %s;", refVar, c.translateExpr(expr)) translateCond := func(cond ast.Expr) *expression { if types.Identical(c.p.Types[cond].Type, types.Typ[types.UntypedNil]) { return c.formatExpr("%s === $ifaceNil", refVar) } return c.formatExpr("$assertType(%s, %s, true)[1]", refVar, c.typeName(c.p.Types[cond].Type)) } c.translateBranchingStmt(s.Body.List, true, translateCond, printCaseBodyPrefix, label, c.Flattened[s]) case *ast.ForStmt: if s.Init != nil { c.translateStmt(s.Init, nil) } cond := func() string { if s.Cond == nil { return "true" } return c.translateExpr(s.Cond).String() } c.translateLoopingStmt(cond, s.Body, nil, func() { if s.Post != nil { c.translateStmt(s.Post, nil) } }, label, c.Flattened[s]) case *ast.RangeStmt: refVar := c.newVariable("_ref") c.Printf("%s = %s;", refVar, c.translateExpr(s.X)) switch t := c.p.Types[s.X].Type.Underlying().(type) { case *types.Basic: iVar := c.newVariable("_i") c.Printf("%s = 0;", iVar) runeVar := c.newVariable("_rune") c.translateLoopingStmt(func() string { return iVar + " < " + refVar + ".length" }, s.Body, func() { c.Printf("%s = $decodeRune(%s, %s);", runeVar, refVar, iVar) if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, iVar, types.Typ[types.Int], s.Tok == token.DEFINE)) } if !isBlank(s.Value) { c.Printf("%s", c.translateAssign(s.Value, runeVar+"[0]", types.Typ[types.Rune], s.Tok == token.DEFINE)) } }, func() { c.Printf("%s += %s[1];", iVar, runeVar) }, label, c.Flattened[s]) case *types.Map: iVar := c.newVariable("_i") c.Printf("%s = 0;", iVar) keysVar := c.newVariable("_keys") c.Printf("%s = $keys(%s);", keysVar, refVar) c.translateLoopingStmt(func() string { return iVar + " < " + keysVar + ".length" }, s.Body, func() { entryVar := c.newVariable("_entry") c.Printf("%s = %s[%s[%s]];", entryVar, refVar, keysVar, iVar) c.translateStmt(&ast.IfStmt{ Cond: c.newIdent(entryVar+" === undefined", types.Typ[types.Bool]), Body: &ast.BlockStmt{List: []ast.Stmt{&ast.BranchStmt{Tok: token.CONTINUE}}}, }, nil) if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, entryVar+".k", t.Key(), s.Tok == token.DEFINE)) } if !isBlank(s.Value) { c.Printf("%s", c.translateAssign(s.Value, entryVar+".v", t.Elem(), s.Tok == token.DEFINE)) } }, func() { c.Printf("%s++;", iVar) }, label, c.Flattened[s]) case *types.Array, *types.Pointer, *types.Slice: var length string var elemType types.Type switch t2 := t.(type) { case *types.Array: length = fmt.Sprintf("%d", t2.Len()) elemType = t2.Elem() case *types.Pointer: length = fmt.Sprintf("%d", t2.Elem().Underlying().(*types.Array).Len()) elemType = t2.Elem().Underlying().(*types.Array).Elem() case *types.Slice: length = refVar + ".$length" elemType = t2.Elem() } iVar := c.newVariable("_i") c.Printf("%s = 0;", iVar) c.translateLoopingStmt(func() string { return iVar + " < " + length }, s.Body, func() { if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, iVar, types.Typ[types.Int], s.Tok == token.DEFINE)) } if !isBlank(s.Value) { c.Printf("%s", c.translateAssign(s.Value, c.translateImplicitConversion(c.setType(&ast.IndexExpr{ X: c.newIdent(refVar, t), Index: c.newIdent(iVar, types.Typ[types.Int]), }, elemType), elemType).String(), elemType, s.Tok == token.DEFINE)) } }, func() { c.Printf("%s++;", iVar) }, label, c.Flattened[s]) case *types.Chan: okVar := c.newIdent(c.newVariable("_ok"), types.Typ[types.Bool]) key := s.Key tok := s.Tok if key == nil { key = ast.NewIdent("_") tok = token.ASSIGN } forStmt := &ast.ForStmt{ Body: &ast.BlockStmt{ List: []ast.Stmt{ &ast.AssignStmt{ Lhs: []ast.Expr{ key, okVar, }, Rhs: []ast.Expr{ c.setType(&ast.UnaryExpr{X: c.newIdent(refVar, t), Op: token.ARROW}, types.NewTuple(types.NewVar(0, nil, "", t.Elem()), types.NewVar(0, nil, "", types.Typ[types.Bool]))), }, Tok: tok, }, &ast.IfStmt{ Cond: &ast.UnaryExpr{X: okVar, Op: token.NOT}, Body: &ast.BlockStmt{List: []ast.Stmt{&ast.BranchStmt{Tok: token.BREAK}}}, }, s.Body, }, }, } c.Flattened[forStmt] = true c.translateStmt(forStmt, label) default: panic("") } case *ast.BranchStmt: normalLabel := "" blockingLabel := "" data := c.flowDatas[nil] if s.Label != nil { normalLabel = " " + s.Label.Name blockingLabel = " s" // use explicit label "s", because surrounding loop may not be flattened data = c.flowDatas[c.p.Uses[s.Label].(*types.Label)] } switch s.Tok { case token.BREAK: c.PrintCond(data.endCase == 0, fmt.Sprintf("break%s;", normalLabel), fmt.Sprintf("$s = %d; continue%s;", data.endCase, blockingLabel)) case token.CONTINUE: data.postStmt() c.PrintCond(data.beginCase == 0, fmt.Sprintf("continue%s;", normalLabel), fmt.Sprintf("$s = %d; continue%s;", data.beginCase, blockingLabel)) case token.GOTO: c.PrintCond(false, "goto "+s.Label.Name, fmt.Sprintf("$s = %d; continue;", c.labelCase(c.p.Uses[s.Label].(*types.Label)))) case token.FALLTHROUGH: // handled in CaseClause default: panic("Unhandled branch statment: " + s.Tok.String()) } case *ast.ReturnStmt: results := s.Results if c.resultNames != nil { if len(s.Results) != 0 { c.translateStmt(&ast.AssignStmt{ Lhs: c.resultNames, Tok: token.ASSIGN, Rhs: s.Results, }, nil) } results = c.resultNames } c.Printf("return%s;", c.translateResults(results)) case *ast.DeferStmt: isBuiltin := false isJs := false switch fun := s.Call.Fun.(type) { case *ast.Ident: var builtin *types.Builtin builtin, isBuiltin = c.p.Uses[fun].(*types.Builtin) if isBuiltin && builtin.Name() == "recover" { c.Printf("$deferred.push([$recover, []]);") return } case *ast.SelectorExpr: isJs = typesutil.IsJsPackage(c.p.Uses[fun.Sel].Pkg()) } sig := c.p.Types[s.Call.Fun].Type.Underlying().(*types.Signature) args := c.translateArgs(sig, s.Call.Args, s.Call.Ellipsis.IsValid(), true) if isBuiltin || isJs { vars := make([]string, len(s.Call.Args)) callArgs := make([]ast.Expr, len(s.Call.Args)) for i, arg := range s.Call.Args { v := c.newVariable("_arg") vars[i] = v callArgs[i] = c.newIdent(v, c.p.Types[arg].Type) } call := c.translateExpr(&ast.CallExpr{ Fun: s.Call.Fun, Args: callArgs, Ellipsis: s.Call.Ellipsis, }) c.Printf("$deferred.push([function(%s) { %s; }, [%s]]);", strings.Join(vars, ", "), call, strings.Join(args, ", ")) return } c.Printf("$deferred.push([%s, [%s]]);", c.translateExpr(s.Call.Fun), strings.Join(args, ", ")) case *ast.AssignStmt: if s.Tok != token.ASSIGN && s.Tok != token.DEFINE { panic(s.Tok) } if s.Tok == token.DEFINE { for _, lhs := range s.Lhs { if !isBlank(lhs) { obj := c.p.Defs[lhs.(*ast.Ident)] if obj == nil { obj = c.p.Uses[lhs.(*ast.Ident)] } c.setType(lhs, obj.Type()) } } } switch { case len(s.Lhs) == 1 && len(s.Rhs) == 1: lhs := astutil.RemoveParens(s.Lhs[0]) if isBlank(lhs) { if analysis.HasSideEffect(s.Rhs[0], c.p.Info.Info) { c.Printf("%s;", c.translateExpr(s.Rhs[0]).String()) } return } lhsType := c.p.Types[s.Lhs[0]].Type c.Printf("%s", c.translateAssignOfExpr(lhs, s.Rhs[0], lhsType, s.Tok == token.DEFINE)) case len(s.Lhs) > 1 && len(s.Rhs) == 1: tupleVar := c.newVariable("_tuple") out := tupleVar + " = " + c.translateExpr(s.Rhs[0]).String() + ";" tuple := c.p.Types[s.Rhs[0]].Type.(*types.Tuple) for i, lhs := range s.Lhs { lhs = astutil.RemoveParens(lhs) if !isBlank(lhs) { lhsType := c.p.Types[s.Lhs[i]].Type out += " " + c.translateAssignOfExpr(lhs, c.newIdent(fmt.Sprintf("%s[%d]", tupleVar, i), tuple.At(i).Type()), lhsType, s.Tok == token.DEFINE) } } c.Printf("%s", out) case len(s.Lhs) == len(s.Rhs): tmpVars := make([]string, len(s.Rhs)) var parts []string for i, rhs := range s.Rhs { tmpVars[i] = c.newVariable("_tmp") if isBlank(astutil.RemoveParens(s.Lhs[i])) { if analysis.HasSideEffect(rhs, c.p.Info.Info) { c.Printf("%s;", c.translateExpr(rhs).String()) } continue } lhsType := c.p.Types[s.Lhs[i]].Type parts = append(parts, c.translateAssignOfExpr(c.newIdent(tmpVars[i], c.p.Types[s.Lhs[i]].Type), rhs, lhsType, true)) } for i, lhs := range s.Lhs { lhs = astutil.RemoveParens(lhs) if !isBlank(lhs) { t := c.p.Types[lhs].Type parts = append(parts, c.translateAssignOfExpr(lhs, c.newIdent(tmpVars[i], t), t, s.Tok == token.DEFINE)) } } c.Printf("%s", strings.Join(parts, " ")) default: panic("Invalid arity of AssignStmt.") } case *ast.DeclStmt: decl := s.Decl.(*ast.GenDecl) switch decl.Tok { case token.VAR: for _, spec := range s.Decl.(*ast.GenDecl).Specs { valueSpec := spec.(*ast.ValueSpec) lhs := make([]ast.Expr, len(valueSpec.Names)) for i, name := range valueSpec.Names { lhs[i] = name } rhs := valueSpec.Values isTuple := false if len(rhs) == 1 { _, isTuple = c.p.Types[rhs[0]].Type.(*types.Tuple) } for len(rhs) < len(lhs) && !isTuple { rhs = append(rhs, nil) } c.translateStmt(&ast.AssignStmt{ Lhs: lhs, Tok: token.DEFINE, Rhs: rhs, }, nil) } case token.TYPE: for _, spec := range decl.Specs { o := c.p.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName) c.p.typeNames = append(c.p.typeNames, o) c.p.objectNames[o] = c.newVariableWithLevel(o.Name(), true) c.p.dependencies[o] = true } case token.CONST: // skip, constants are inlined } case *ast.ExprStmt: expr := c.translateExpr(s.X) if expr != nil && expr.String() != "" { c.Printf("%s;", expr) } case *ast.LabeledStmt: label := c.p.Defs[s.Label].(*types.Label) if c.GotoLabel[label] { c.PrintCond(false, s.Label.Name+":", fmt.Sprintf("case %d:", c.labelCase(label))) } c.translateStmt(s.Stmt, label) case *ast.GoStmt: c.Printf("$go(%s, [%s]);", c.translateExpr(s.Call.Fun), strings.Join(c.translateArgs(c.p.Types[s.Call.Fun].Type.Underlying().(*types.Signature), s.Call.Args, s.Call.Ellipsis.IsValid(), false), ", ")) case *ast.SendStmt: chanType := c.p.Types[s.Chan].Type.Underlying().(*types.Chan) call := &ast.CallExpr{ Fun: c.newIdent("$send", types.NewSignature(nil, types.NewTuple(types.NewVar(0, nil, "", chanType), types.NewVar(0, nil, "", chanType.Elem())), nil, false)), Args: []ast.Expr{s.Chan, s.Value}, } c.Blocking[call] = true c.translateStmt(&ast.ExprStmt{X: call}, label) case *ast.SelectStmt: var channels []string var caseClauses []ast.Stmt flattened := false hasDefault := false for i, s := range s.Body.List { clause := s.(*ast.CommClause) switch comm := clause.Comm.(type) { case nil: channels = append(channels, "[]") hasDefault = true case *ast.ExprStmt: channels = append(channels, c.formatExpr("[%e]", astutil.RemoveParens(comm.X).(*ast.UnaryExpr).X).String()) case *ast.AssignStmt: channels = append(channels, c.formatExpr("[%e]", astutil.RemoveParens(comm.Rhs[0]).(*ast.UnaryExpr).X).String()) case *ast.SendStmt: channels = append(channels, c.formatExpr("[%e, %e]", comm.Chan, comm.Value).String()) default: panic(fmt.Sprintf("unhandled: %T", comm)) } indexLit := &ast.BasicLit{Kind: token.INT} c.p.Types[indexLit] = types.TypeAndValue{Type: types.Typ[types.Int], Value: exact.MakeInt64(int64(i))} caseClauses = append(caseClauses, &ast.CaseClause{ List: []ast.Expr{indexLit}, Body: clause.Body, }) flattened = flattened || c.Flattened[clause] } selectCall := c.setType(&ast.CallExpr{ Fun: c.newIdent("$select", types.NewSignature(nil, types.NewTuple(types.NewVar(0, nil, "", types.NewInterface(nil, nil))), types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int])), false)), Args: []ast.Expr{c.newIdent(fmt.Sprintf("[%s]", strings.Join(channels, ", ")), types.NewInterface(nil, nil))}, }, types.Typ[types.Int]) c.Blocking[selectCall] = !hasDefault selectionVar := c.newVariable("_selection") c.Printf("%s = %s;", selectionVar, c.translateExpr(selectCall)) translateCond := func(cond ast.Expr) *expression { return c.formatExpr("%s[0] === %e", selectionVar, cond) } printCaseBodyPrefix := func(index int) { if assign, ok := s.Body.List[index].(*ast.CommClause).Comm.(*ast.AssignStmt); ok { switch rhsType := c.p.Types[assign.Rhs[0]].Type.(type) { case *types.Tuple: c.translateStmt(&ast.AssignStmt{Lhs: assign.Lhs, Rhs: []ast.Expr{c.newIdent(selectionVar+"[1]", rhsType)}, Tok: assign.Tok}, nil) default: c.translateStmt(&ast.AssignStmt{Lhs: assign.Lhs, Rhs: []ast.Expr{c.newIdent(selectionVar+"[1][0]", rhsType)}, Tok: assign.Tok}, nil) } } } c.translateBranchingStmt(caseClauses, true, translateCond, printCaseBodyPrefix, label, flattened) case *ast.EmptyStmt: // skip default: panic(fmt.Sprintf("Unhandled statement: %T\n", s)) } }
func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, importContext *ImportContext, minify bool) (*Archive, error) { typesInfo := &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), Scopes: make(map[ast.Node]*types.Scope), } var importError error var errList ErrorList var previousErr error config := &types.Config{ Importer: packageImporter{ importContext: importContext, importError: &importError, }, 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, typesInfo) if importError != nil { return nil, importError } if errList != nil { if len(errList) > 10 { pos := token.NoPos if last, ok := errList[9].(types.Error); ok { pos = last.Pos } errList = append(errList[:10], types.Error{Fset: fileSet, Pos: pos, Msg: "too many errors"}) } return nil, errList } if err != nil { return nil, err } importContext.Packages[importPath] = typesPkg exportData := importer.ExportData(typesPkg) encodedFileSet := bytes.NewBuffer(nil) if err := fileSet.Write(json.NewEncoder(encodedFileSet).Encode); err != nil { return nil, err } isBlocking := func(f *types.Func) bool { archive, err := importContext.Import(f.Pkg().Path()) if err != nil { panic(err) } fullName := f.FullName() for _, d := range archive.Declarations { if string(d.FullName) == fullName { return d.Blocking } } panic(fullName) } pkgInfo := analysis.AnalyzePkg(files, fileSet, typesInfo, typesPkg, isBlocking) c := &funcContext{ FuncInfo: pkgInfo.InitFuncInfo, p: &pkgContext{ Info: pkgInfo, additionalSelections: make(map[*ast.SelectorExpr]selection), pkgVars: make(map[string]string), objectNames: make(map[types.Object]string), varPtrNames: make(map[*types.Var]string), escapingVars: make(map[*types.Var]bool), indentation: 1, dependencies: make(map[types.Object]bool), minify: minify, fileSet: fileSet, }, allVars: make(map[string]int), flowDatas: map[*types.Label]*flowData{nil: {}}, caseCounter: 1, labelCases: make(map[*types.Label]int), } for name := range reservedKeywords { c.allVars[name] = 1 } // imports var importDecls []*Decl var importedPaths []string for _, importedPkg := range typesPkg.Imports() { c.p.pkgVars[importedPkg.Path()] = c.newVariableWithLevel(importedPkg.Name(), true) importedPaths = append(importedPaths, importedPkg.Path()) } sort.Strings(importedPaths) for _, impPath := range importedPaths { id := c.newIdent(fmt.Sprintf(`%s.$init`, c.p.pkgVars[impPath]), types.NewSignature(nil, nil, nil, false)) call := &ast.CallExpr{Fun: id} c.Blocking[call] = true c.Flattened[call] = true importDecls = append(importDecls, &Decl{ Vars: []string{c.p.pkgVars[impPath]}, DeclCode: []byte(fmt.Sprintf("\t%s = $packages[\"%s\"];\n", c.p.pkgVars[impPath], impPath)), InitCode: c.CatchOutput(1, func() { c.translateStmt(&ast.ExprStmt{X: call}, nil) }), }) } var functions []*ast.FuncDecl var vars []*types.Var for _, file := range files { for _, decl := range file.Decls { switch d := decl.(type) { case *ast.FuncDecl: sig := c.p.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 sig.Recv() == nil { c.objectName(c.p.Defs[d.Name].(*types.Func)) // 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.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName) c.p.typeNames = append(c.p.typeNames, 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.Defs[name].(*types.Var) vars = append(vars, o) c.objectName(o) // register toplevel name } } } case token.CONST: // skip, constants are inlined } } } } collectDependencies := func(f func()) []string { c.p.dependencies = make(map[types.Object]bool) f() var deps []string for o := range c.p.dependencies { qualifiedName := o.Pkg().Path() + "." + o.Name() if f, ok := o.(*types.Func); ok && f.Type().(*types.Signature).Recv() != nil { deps = append(deps, qualifiedName+"~") continue } deps = append(deps, qualifiedName) } sort.Strings(deps) return deps } // variables var varDecls []*Decl varsWithInit := make(map[*types.Var]bool) for _, init := range c.p.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 c.p.HasPointer[o] && !o.Exported() { d.Vars = append(d.Vars, c.varPtrName(o)) } if _, ok := varsWithInit[o]; !ok { d.DceDeps = collectDependencies(func() { d.InitCode = []byte(fmt.Sprintf("\t\t%s = %s;\n", c.objectName(o), c.translateExpr(c.zeroValue(o.Type())).String())) }) } d.DceObjectFilter = o.Name() varDecls = append(varDecls, &d) } for _, init := range c.p.InitOrder { lhs := make([]ast.Expr, len(init.Lhs)) for i, o := range init.Lhs { ident := ast.NewIdent(o.Name()) c.p.Defs[ident] = o lhs[i] = c.setType(ident, o.Type()) varsWithInit[o] = true } var d Decl d.DceDeps = collectDependencies(func() { c.localVars = nil d.InitCode = c.CatchOutput(1, func() { c.translateStmt(&ast.AssignStmt{ Lhs: lhs, Tok: token.DEFINE, Rhs: []ast.Expr{init.Rhs}, }, nil) }) d.Vars = append(d.Vars, c.localVars...) }) if len(init.Lhs) == 1 { if !analysis.HasSideEffect(init.Rhs, c.p.Info.Info) { d.DceObjectFilter = init.Lhs[0].Name() } } varDecls = append(varDecls, &d) } // functions var funcDecls []*Decl var mainFunc *types.Func for _, fun := range functions { o := c.p.Defs[fun.Name].(*types.Func) funcInfo := c.p.FuncDeclInfos[o] d := Decl{ FullName: o.FullName(), Blocking: len(funcInfo.Blocking) != 0, } if fun.Recv == nil { d.Vars = []string{c.objectName(o)} d.DceObjectFilter = o.Name() switch o.Name() { case "main": mainFunc = o d.DceObjectFilter = "" case "init": d.InitCode = c.CatchOutput(1, func() { id := c.newIdent("", types.NewSignature(nil, nil, nil, false)) c.p.Uses[id] = o call := &ast.CallExpr{Fun: id} if len(c.p.FuncDeclInfos[o].Blocking) != 0 { c.Blocking[call] = true } c.translateStmt(&ast.ExprStmt{X: call}, nil) }) d.DceObjectFilter = "" } } 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.DceObjectFilter = namedRecvType.Obj().Name() if !fun.Name.IsExported() { d.DceMethodFilter = o.Name() + "~" } } d.DceDeps = collectDependencies(func() { d.DeclCode = c.translateToplevelFunction(fun, funcInfo) }) funcDecls = append(funcDecls, &d) } if typesPkg.Name() == "main" { if mainFunc == nil { return nil, fmt.Errorf("missing main function") } id := c.newIdent("", types.NewSignature(nil, nil, nil, false)) c.p.Uses[id] = mainFunc call := &ast.CallExpr{Fun: id} ifStmt := &ast.IfStmt{ Cond: c.newIdent("$pkg === $mainPkg", types.Typ[types.Bool]), Body: &ast.BlockStmt{ List: []ast.Stmt{ &ast.ExprStmt{X: call}, &ast.AssignStmt{ Lhs: []ast.Expr{c.newIdent("$mainFinished", types.Typ[types.Bool])}, Tok: token.ASSIGN, Rhs: []ast.Expr{c.newConst(types.Typ[types.Bool], constant.MakeBool(true))}, }, }, }, } if len(c.p.FuncDeclInfos[mainFunc].Blocking) != 0 { c.Blocking[call] = true c.Flattened[ifStmt] = true } funcDecls = append(funcDecls, &Decl{ InitCode: c.CatchOutput(1, func() { c.translateStmt(ifStmt, nil) }), }) } // named types var typeDecls []*Decl for _, o := range c.p.typeNames { typeName := c.objectName(o) d := Decl{ Vars: []string{typeName}, DceObjectFilter: o.Name(), } d.DceDeps = collectDependencies(func() { d.DeclCode = c.CatchOutput(0, func() { typeName := c.objectName(o) lhs := typeName if isPkgLevel(o) { lhs += " = $pkg." + encodeIdent(o.Name()) } size := int64(0) constructor := "null" switch t := o.Type().Underlying().(type) { case *types.Struct: params := make([]string, t.NumFields()) for i := 0; i < t.NumFields(); i++ { params[i] = fieldName(t, i) + "_" } constructor = fmt.Sprintf("function(%s) {\n\t\tthis.$val = this;\n\t\tif (arguments.length === 0) {\n", strings.Join(params, ", ")) for i := 0; i < t.NumFields(); i++ { constructor += fmt.Sprintf("\t\t\tthis.%s = %s;\n", fieldName(t, i), c.translateExpr(c.zeroValue(t.Field(i).Type())).String()) } constructor += "\t\t\treturn;\n\t\t}\n" for i := 0; i < t.NumFields(); i++ { constructor += fmt.Sprintf("\t\tthis.%[1]s = %[1]s_;\n", fieldName(t, i)) } constructor += "\t}" case *types.Basic, *types.Array, *types.Slice, *types.Chan, *types.Signature, *types.Interface, *types.Pointer, *types.Map: size = sizes32.Sizeof(t) } c.Printf(`%s = $newType(%d, %s, "%s.%s", "%s", "%s", %s);`, lhs, size, typeKind(o.Type()), o.Pkg().Name(), o.Name(), o.Name(), o.Pkg().Path(), constructor) }) d.MethodListCode = c.CatchOutput(0, func() { if _, isInterface := o.Type().Underlying().(*types.Interface); !isInterface { named := o.Type().(*types.Named) var methods []string var ptrMethods []string for i := 0; i < named.NumMethods(); i++ { method := named.Method(i) name := method.Name() if reservedKeywords[name] { name += "$" } pkgPath := "" if !method.Exported() { pkgPath = method.Pkg().Path() } t := method.Type().(*types.Signature) entry := fmt.Sprintf(`{prop: "%s", name: "%s", pkg: "%s", typ: $funcType(%s)}`, name, method.Name(), pkgPath, c.initArgs(t)) if _, isPtr := t.Recv().Type().(*types.Pointer); isPtr { ptrMethods = append(ptrMethods, entry) continue } methods = append(methods, entry) } if len(methods) > 0 { c.Printf("%s.methods = [%s];", c.typeName(o.Type()), strings.Join(methods, ", ")) } if len(ptrMethods) > 0 { c.Printf("%s.methods = [%s];", c.typeName(types.NewPointer(o.Type())), strings.Join(ptrMethods, ", ")) } } }) switch t := o.Type().Underlying().(type) { case *types.Array, *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Slice, *types.Signature, *types.Struct: d.TypeInitCode = c.CatchOutput(0, func() { c.Printf("%s.init(%s);", c.objectName(o), c.initArgs(t)) }) } }) typeDecls = append(typeDecls, &d) } // anonymous types for _, t := range c.p.anonTypes { d := Decl{ Vars: []string{t.Name()}, DceObjectFilter: t.Name(), } d.DceDeps = collectDependencies(func() { d.DeclCode = []byte(fmt.Sprintf("\t%s = $%sType(%s);\n", t.Name(), strings.ToLower(typeKind(t.Type())[5:]), c.initArgs(t.Type()))) }) typeDecls = append(typeDecls, &d) } var allDecls []*Decl for _, d := range append(append(append(importDecls, typeDecls...), varDecls...), funcDecls...) { d.DeclCode = removeWhitespace(d.DeclCode, minify) d.MethodListCode = removeWhitespace(d.MethodListCode, minify) d.TypeInitCode = removeWhitespace(d.TypeInitCode, minify) d.InitCode = removeWhitespace(d.InitCode, minify) allDecls = append(allDecls, d) } if len(c.p.errList) != 0 { return nil, c.p.errList } return &Archive{ ImportPath: importPath, Name: typesPkg.Name(), Imports: importedPaths, ExportData: exportData, Declarations: allDecls, FileSet: encodedFileSet.Bytes(), Minified: minify, types: typesPkg, }, nil }