blob: d4ca7647130e41767f12162129bbb7a957f60730 [file] [log] [blame]
package compiler
import (
"fmt"
"go/ast"
"go/constant"
"go/printer"
"go/token"
"go/types"
"strings"
"github.com/gopherjs/gopherjs/compiler/analysis"
"github.com/gopherjs/gopherjs/compiler/astutil"
"github.com/gopherjs/gopherjs/compiler/filter"
"github.com/gopherjs/gopherjs/compiler/typesutil"
)
func (fc *funcContext) translateStmtList(stmts []ast.Stmt) {
for _, stmt := range stmts {
fc.translateStmt(stmt, nil)
}
fc.SetPos(token.NoPos)
}
func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) {
defer func() {
err := recover()
if err == nil {
return
}
if _, yes := bailingOut(err); yes {
panic(err) // Continue orderly bailout.
}
// Oh noes, we've tried to compile something so bad that compiler panicked
// and ran away. Let's gather some debugging clues.
bail := bailout(err)
pos := stmt.Pos()
if fc.posAvailable && fc.pos.IsValid() {
pos = fc.pos
}
fmt.Fprintf(bail, "Occurred while compiling statement at %s:\n", fc.pkgCtx.fileSet.Position(pos))
(&printer.Config{Tabwidth: 2, Indent: 1, Mode: printer.UseSpaces}).Fprint(bail, fc.pkgCtx.fileSet, stmt)
fmt.Fprintf(bail, "\n\nDetailed AST:\n")
ast.Fprint(bail, fc.pkgCtx.fileSet, stmt, ast.NotNilFilter)
panic(bail) // Initiate orderly bailout.
}()
fc.SetPos(stmt.Pos())
stmt = filter.IncDecStmt(stmt, fc.pkgCtx.Info.Info)
stmt = filter.Assign(stmt, fc.pkgCtx.Info.Info, fc.pkgCtx.Info.Pkg)
switch s := stmt.(type) {
case *ast.BlockStmt:
fc.translateStmtList(s.List)
case *ast.IfStmt:
var caseClauses []*ast.CaseClause
ifStmt := s
for {
if ifStmt.Init != nil {
panic("simplification error")
}
caseClauses = append(caseClauses, &ast.CaseClause{List: []ast.Expr{ifStmt.Cond}, Body: ifStmt.Body.List})
elseStmt, ok := ifStmt.Else.(*ast.IfStmt)
if !ok {
break
}
ifStmt = elseStmt
}
var defaultClause *ast.CaseClause
if block, ok := ifStmt.Else.(*ast.BlockStmt); ok {
defaultClause = &ast.CaseClause{Body: block.List}
}
fc.translateBranchingStmt(caseClauses, defaultClause, false, fc.translateExpr, nil, fc.Flattened[s])
case *ast.SwitchStmt:
if s.Init != nil || s.Tag != nil || len(s.Body.List) != 1 {
panic("simplification error")
}
clause := s.Body.List[0].(*ast.CaseClause)
if len(clause.List) != 0 {
panic("simplification error")
}
prevFlowData := fc.flowDatas[nil]
data := &flowData{
postStmt: prevFlowData.postStmt, // for "continue" of outer loop
beginCase: prevFlowData.beginCase, // same
}
fc.flowDatas[nil] = data
fc.flowDatas[label] = data
defer func() {
delete(fc.flowDatas, label)
fc.flowDatas[nil] = prevFlowData
}()
if fc.Flattened[s] {
data.endCase = fc.caseCounter
fc.caseCounter++
fc.Indented(func() {
fc.translateStmtList(clause.Body)
})
fc.Printf("case %d:", data.endCase)
return
}
if label != nil || analysis.HasBreak(clause) {
if label != nil {
fc.Printf("%s:", label.Name())
}
fc.Printf("switch (0) { default:")
fc.Indented(func() {
fc.translateStmtList(clause.Body)
})
fc.Printf("}")
return
}
fc.translateStmtList(clause.Body)
case *ast.TypeSwitchStmt:
if s.Init != nil {
fc.translateStmt(s.Init, nil)
}
refVar := fc.newLocalVariable("_ref")
var expr ast.Expr
switch a := s.Assign.(type) {
case *ast.AssignStmt:
expr = a.Rhs[0].(*ast.TypeAssertExpr).X
case *ast.ExprStmt:
expr = a.X.(*ast.TypeAssertExpr).X
}
fc.Printf("%s = %s;", refVar, fc.translateExpr(expr))
translateCond := func(cond ast.Expr) *expression {
if types.Identical(fc.typeOf(cond), types.Typ[types.UntypedNil]) {
return fc.formatExpr("%s === $ifaceNil", refVar)
}
return fc.formatExpr("$assertType(%s, %s, true)[1]", refVar, fc.typeName(fc.typeOf(cond)))
}
var caseClauses []*ast.CaseClause
var defaultClause *ast.CaseClause
for _, cc := range s.Body.List {
clause := cc.(*ast.CaseClause)
var bodyPrefix []ast.Stmt
if implicit := fc.pkgCtx.Implicits[clause]; implicit != nil {
typ := fc.typeResolver.Substitute(implicit.Type())
value := refVar
if typesutil.IsJsObject(typ.Underlying()) {
value += ".$val.object"
} else if _, ok := typ.Underlying().(*types.Interface); !ok {
value += ".$val"
}
bodyPrefix = []ast.Stmt{&ast.AssignStmt{
Lhs: []ast.Expr{fc.newIdent(fc.objectName(implicit), typ)},
Tok: token.DEFINE,
Rhs: []ast.Expr{fc.newIdent(value, typ)},
}}
}
c := &ast.CaseClause{
List: clause.List,
Body: append(bodyPrefix, clause.Body...),
}
if len(c.List) == 0 {
defaultClause = c
continue
}
caseClauses = append(caseClauses, c)
}
fc.translateBranchingStmt(caseClauses, defaultClause, true, translateCond, label, fc.Flattened[s])
case *ast.ForStmt:
if s.Init != nil {
fc.translateStmt(s.Init, nil)
}
cond := func() string {
if s.Cond == nil {
return "true"
}
return fc.translateExpr(s.Cond).String()
}
fc.translateLoopingStmt(cond, s.Body, nil, func() {
if s.Post != nil {
fc.translateStmt(s.Post, nil)
}
}, label, fc.Flattened[s])
case *ast.RangeStmt:
refVar := fc.newLocalVariable("_ref")
fc.Printf("%s = %s;", refVar, fc.translateExpr(s.X))
switch t := fc.typeOf(s.X).Underlying().(type) {
case *types.Basic:
iVar := fc.newLocalVariable("_i")
fc.Printf("%s = 0;", iVar)
runeVar := fc.newLocalVariable("_rune")
fc.translateLoopingStmt(func() string { return iVar + " < " + refVar + ".length" }, s.Body, func() {
fc.Printf("%s = $decodeRune(%s, %s);", runeVar, refVar, iVar)
if !isBlank(s.Key) {
fc.Printf("%s", fc.translateAssign(s.Key, fc.newIdent(iVar, types.Typ[types.Int]), s.Tok == token.DEFINE))
}
if !isBlank(s.Value) {
fc.Printf("%s", fc.translateAssign(s.Value, fc.newIdent(runeVar+"[0]", types.Typ[types.Rune]), s.Tok == token.DEFINE))
}
}, func() {
fc.Printf("%s += %s[1];", iVar, runeVar)
}, label, fc.Flattened[s])
case *types.Map:
iVar := fc.newLocalVariable("_i")
fc.Printf("%s = 0;", iVar)
keysVar := fc.newLocalVariable("_keys")
fc.Printf("%s = %s ? %s.keys() : undefined;", keysVar, refVar, refVar)
sizeVar := fc.newLocalVariable("_size")
fc.Printf("%s = %s ? %s.size : 0;", sizeVar, refVar, refVar)
fc.translateLoopingStmt(func() string { return iVar + " < " + sizeVar }, s.Body, func() {
keyVar := fc.newLocalVariable("_key")
entryVar := fc.newLocalVariable("_entry")
fc.Printf("%s = %s.next().value;", keyVar, keysVar)
fc.Printf("%s = %s.get(%s);", entryVar, refVar, keyVar)
fc.translateStmt(&ast.IfStmt{
Cond: fc.newIdent(entryVar+" === undefined", types.Typ[types.Bool]),
Body: &ast.BlockStmt{List: []ast.Stmt{&ast.BranchStmt{Tok: token.CONTINUE}}},
}, nil)
if !isBlank(s.Key) {
fc.Printf("%s", fc.translateAssign(s.Key, fc.newIdent(entryVar+".k", t.Key()), s.Tok == token.DEFINE))
}
if !isBlank(s.Value) {
fc.Printf("%s", fc.translateAssign(s.Value, fc.newIdent(entryVar+".v", t.Elem()), s.Tok == token.DEFINE))
}
}, func() {
fc.Printf("%s++;", iVar)
}, label, fc.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 := fc.newLocalVariable("_i")
fc.Printf("%s = 0;", iVar)
fc.translateLoopingStmt(func() string { return iVar + " < " + length }, s.Body, func() {
if !isBlank(s.Key) {
fc.Printf("%s", fc.translateAssign(s.Key, fc.newIdent(iVar, types.Typ[types.Int]), s.Tok == token.DEFINE))
}
if !isBlank(s.Value) {
fc.Printf("%s", fc.translateAssign(s.Value, fc.setType(&ast.IndexExpr{
X: fc.newIdent(refVar, t),
Index: fc.newIdent(iVar, types.Typ[types.Int]),
}, elemType), s.Tok == token.DEFINE))
}
}, func() {
fc.Printf("%s++;", iVar)
}, label, fc.Flattened[s])
case *types.Chan:
okVar := fc.newIdent(fc.newLocalVariable("_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{
fc.setType(&ast.UnaryExpr{X: fc.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,
},
},
}
fc.Flattened[forStmt] = true
fc.translateStmt(forStmt, label)
default:
panic("")
}
case *ast.BranchStmt:
normalLabel := ""
blockingLabel := ""
data := fc.flowDatas[nil]
if s.Label != nil {
normalLabel = " " + s.Label.Name
blockingLabel = " s" // use explicit label "s", because surrounding loop may not be flattened
data = fc.flowDatas[fc.pkgCtx.Uses[s.Label].(*types.Label)]
}
switch s.Tok {
case token.BREAK:
fc.PrintCond(data.endCase == 0, fmt.Sprintf("break%s;", normalLabel), fmt.Sprintf("$s = %d; continue%s;", data.endCase, blockingLabel))
case token.CONTINUE:
data.postStmt()
fc.PrintCond(data.beginCase == 0, fmt.Sprintf("continue%s;", normalLabel), fmt.Sprintf("$s = %d; continue%s;", data.beginCase, blockingLabel))
case token.GOTO:
fc.PrintCond(false, "goto "+s.Label.Name, fmt.Sprintf("$s = %d; continue;", fc.labelCase(fc.pkgCtx.Uses[s.Label].(*types.Label))))
case token.FALLTHROUGH:
// handled in CaseClause
default:
panic("Unhandled branch statement: " + s.Tok.String())
}
case *ast.ReturnStmt:
results := s.Results
if fc.resultNames != nil {
if len(s.Results) != 0 {
fc.translateStmt(&ast.AssignStmt{
Lhs: fc.resultNames,
Tok: token.ASSIGN,
Rhs: s.Results,
}, nil)
}
results = fc.resultNames
}
rVal := fc.translateResults(results)
if len(fc.Flattened) == 0 {
// The function is not flattened and we don't have to worry about
// resumption. A plain return statement is sufficient.
fc.Printf("return%s;", rVal)
return
}
if !fc.Blocking[s] {
// The function is flattened, but the return statement is non-blocking
// (i.e. doesn't lead to blocking deferred calls). A regular return
// is sufficient, but we also make sure to not resume function body.
fc.Printf("$s = -1; return%s;", rVal)
return
}
if rVal != "" {
// If returned expression is non empty, evaluate and store it in a
// variable to avoid double-execution in case a deferred function blocks.
rVar := fc.newLocalVariable("$r")
fc.Printf("%s =%s;", rVar, rVal)
rVal = " " + rVar
}
// If deferred function is blocking, we need to re-execute return statement
// upon resumption to make sure the returned value is not lost.
// See: https://github.com/gopherjs/gopherjs/issues/603.
nextCase := fc.caseCounter
fc.caseCounter++
fc.Printf("$s = %[1]d; case %[1]d: return%[2]s;", nextCase, rVal)
return
case *ast.DeferStmt:
callable, arglist := fc.delegatedCall(s.Call)
fc.Printf("$deferred.push([%s, %s]);", callable, arglist)
case *ast.AssignStmt:
if s.Tok != token.ASSIGN && s.Tok != token.DEFINE {
panic(s.Tok)
}
switch {
case len(s.Lhs) == 1 && len(s.Rhs) == 1:
lhs := astutil.RemoveParens(s.Lhs[0])
if isBlank(lhs) {
fc.Printf("$unused(%s);", fc.translateImplicitConversion(s.Rhs[0], fc.typeOf(s.Lhs[0])))
return
}
fc.Printf("%s", fc.translateAssign(lhs, s.Rhs[0], s.Tok == token.DEFINE))
case len(s.Lhs) > 1 && len(s.Rhs) == 1:
tupleVar := fc.newLocalVariable("_tuple")
fc.Printf("%s = %s;", tupleVar, fc.translateExpr(s.Rhs[0]))
tuple := fc.typeOf(s.Rhs[0]).(*types.Tuple)
for i, lhs := range s.Lhs {
lhs = astutil.RemoveParens(lhs)
if !isBlank(lhs) {
fc.Printf("%s", fc.translateAssign(lhs, fc.newIdent(fmt.Sprintf("%s[%d]", tupleVar, i), tuple.At(i).Type()), s.Tok == token.DEFINE))
}
}
case len(s.Lhs) == len(s.Rhs):
tmpVars := make([]string, len(s.Rhs))
for i, rhs := range s.Rhs {
tmpVars[i] = fc.newLocalVariable("_tmp")
if isBlank(astutil.RemoveParens(s.Lhs[i])) {
fc.Printf("$unused(%s);", fc.translateExpr(rhs))
continue
}
fc.Printf("%s", fc.translateAssign(fc.newIdent(tmpVars[i], fc.typeOf(s.Lhs[i])), rhs, true))
}
for i, lhs := range s.Lhs {
lhs = astutil.RemoveParens(lhs)
if !isBlank(lhs) {
fc.Printf("%s", fc.translateAssign(lhs, fc.newIdent(tmpVars[i], fc.typeOf(lhs)), s.Tok == token.DEFINE))
}
}
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
if len(rhs) == 0 {
rhs = make([]ast.Expr, len(lhs))
for i, e := range lhs {
rhs[i] = fc.zeroValue(fc.typeOf(e))
}
}
fc.translateStmt(&ast.AssignStmt{
Lhs: lhs,
Tok: token.DEFINE,
Rhs: rhs,
}, nil)
}
case token.TYPE:
for _, spec := range decl.Specs {
o := fc.pkgCtx.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName)
fc.pkgCtx.typeNames.Add(o)
fc.pkgCtx.DeclareDCEDep(o)
}
case token.CONST:
// skip, constants are inlined
}
case *ast.ExprStmt:
expr := fc.translateExpr(s.X)
if expr != nil && expr.String() != "" {
fc.Printf("%s;", expr)
}
case *ast.LabeledStmt:
label := fc.pkgCtx.Defs[s.Label].(*types.Label)
if fc.GotoLabel[label] {
fc.PrintCond(false, s.Label.Name+":", fmt.Sprintf("case %d:", fc.labelCase(label)))
}
fc.translateStmt(s.Stmt, label)
case *ast.GoStmt:
callable, arglist := fc.delegatedCall(s.Call)
fc.Printf("$go(%s, %s);", callable, arglist)
case *ast.SendStmt:
chanType := fc.typeOf(s.Chan).Underlying().(*types.Chan)
call := &ast.CallExpr{
Fun: fc.newIdent("$send", types.NewSignatureType(nil, nil, nil, types.NewTuple(types.NewVar(0, nil, "", chanType), types.NewVar(0, nil, "", chanType.Elem())), nil, false)),
Args: []ast.Expr{s.Chan, fc.newIdent(fc.translateImplicitConversionWithCloning(s.Value, chanType.Elem()).String(), chanType.Elem())},
}
fc.Blocking[call] = true
fc.translateStmt(&ast.ExprStmt{X: call}, label)
case *ast.SelectStmt:
selectionVar := fc.newLocalVariable("_selection")
var channels []string
var caseClauses []*ast.CaseClause
flattened := false
hasDefault := false
for i, cc := range s.Body.List {
clause := cc.(*ast.CommClause)
switch comm := clause.Comm.(type) {
case nil:
channels = append(channels, "[]")
hasDefault = true
case *ast.ExprStmt:
channels = append(channels, fc.formatExpr("[%e]", astutil.RemoveParens(comm.X).(*ast.UnaryExpr).X).String())
case *ast.AssignStmt:
channels = append(channels, fc.formatExpr("[%e]", astutil.RemoveParens(comm.Rhs[0]).(*ast.UnaryExpr).X).String())
case *ast.SendStmt:
chanType := fc.typeOf(comm.Chan).Underlying().(*types.Chan)
channels = append(channels, fc.formatExpr("[%e, %s]", comm.Chan, fc.translateImplicitConversionWithCloning(comm.Value, chanType.Elem())).String())
default:
panic(fmt.Sprintf("unhandled: %T", comm))
}
indexLit := &ast.BasicLit{Kind: token.INT}
fc.pkgCtx.Types[indexLit] = types.TypeAndValue{Type: types.Typ[types.Int], Value: constant.MakeInt64(int64(i))}
var bodyPrefix []ast.Stmt
if assign, ok := clause.Comm.(*ast.AssignStmt); ok {
switch rhsType := fc.typeOf(assign.Rhs[0]).(type) {
case *types.Tuple:
bodyPrefix = []ast.Stmt{&ast.AssignStmt{Lhs: assign.Lhs, Rhs: []ast.Expr{fc.newIdent(selectionVar+"[1]", rhsType)}, Tok: assign.Tok}}
default:
bodyPrefix = []ast.Stmt{&ast.AssignStmt{Lhs: assign.Lhs, Rhs: []ast.Expr{fc.newIdent(selectionVar+"[1][0]", rhsType)}, Tok: assign.Tok}}
}
}
caseClauses = append(caseClauses, &ast.CaseClause{
List: []ast.Expr{indexLit},
Body: append(bodyPrefix, clause.Body...),
})
flattened = flattened || fc.Flattened[clause]
}
selectCall := fc.setType(&ast.CallExpr{
Fun: fc.newIdent("$select", types.NewSignatureType(nil, nil, nil, types.NewTuple(types.NewVar(0, nil, "", types.NewInterfaceType(nil, nil))), types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int])), false)),
Args: []ast.Expr{fc.newIdent(fmt.Sprintf("[%s]", strings.Join(channels, ", ")), types.NewInterfaceType(nil, nil))},
}, types.Typ[types.Int])
if !hasDefault {
fc.Blocking[selectCall] = true
}
fc.Printf("%s = %s;", selectionVar, fc.translateExpr(selectCall))
if len(caseClauses) != 0 {
translateCond := func(cond ast.Expr) *expression {
return fc.formatExpr("%s[0] === %e", selectionVar, cond)
}
fc.translateBranchingStmt(caseClauses, nil, true, translateCond, label, flattened)
}
case *ast.EmptyStmt:
// skip
default:
panic(fmt.Sprintf("Unhandled statement: %T\n", s))
}
}
func (fc *funcContext) translateBranchingStmt(caseClauses []*ast.CaseClause, defaultClause *ast.CaseClause, canBreak bool, translateCond func(ast.Expr) *expression, label *types.Label, flatten bool) {
var caseOffset, defaultCase, endCase int
if flatten {
caseOffset = fc.caseCounter
defaultCase = caseOffset + len(caseClauses)
endCase = defaultCase
if defaultClause != nil {
endCase++
}
fc.caseCounter = endCase + 1
}
hasBreak := false
if canBreak {
prevFlowData := fc.flowDatas[nil]
data := &flowData{
postStmt: prevFlowData.postStmt, // for "continue" of outer loop
beginCase: prevFlowData.beginCase, // same
endCase: endCase,
}
fc.flowDatas[nil] = data
fc.flowDatas[label] = data
defer func() {
delete(fc.flowDatas, label)
fc.flowDatas[nil] = prevFlowData
}()
for _, child := range caseClauses {
if analysis.HasBreak(child) {
hasBreak = true
break
}
}
if defaultClause != nil && analysis.HasBreak(defaultClause) {
hasBreak = true
}
}
if label != nil && !flatten {
fc.Printf("%s:", label.Name())
}
condStrs := make([]string, len(caseClauses))
for i, clause := range caseClauses {
conds := make([]string, len(clause.List))
for j, cond := range clause.List {
conds[j] = translateCond(cond).String()
}
condStrs[i] = strings.Join(conds, " || ")
if flatten {
fc.Printf("/* */ if (%s) { $s = %d; continue; }", condStrs[i], caseOffset+i)
}
}
if flatten {
fc.Printf("/* */ $s = %d; continue;", defaultCase)
}
prefix := ""
suffix := ""
if label != nil || hasBreak {
prefix = "switch (0) { default: "
suffix = " }"
}
for i, clause := range caseClauses {
fc.SetPos(clause.Pos())
fc.PrintCond(!flatten, fmt.Sprintf("%sif (%s) {", prefix, condStrs[i]), fmt.Sprintf("case %d:", caseOffset+i))
fc.Indented(func() {
fc.translateStmtList(clause.Body)
if flatten && (i < len(caseClauses)-1 || defaultClause != nil) && !astutil.EndsWithReturn(clause.Body) {
fc.Printf("$s = %d; continue;", endCase)
}
})
prefix = "} else "
}
if defaultClause != nil {
fc.PrintCond(!flatten, prefix+"{", fmt.Sprintf("case %d:", caseOffset+len(caseClauses)))
fc.Indented(func() {
fc.translateStmtList(defaultClause.Body)
})
}
fc.PrintCond(!flatten, "}"+suffix, fmt.Sprintf("case %d:", endCase))
}
func (fc *funcContext) translateLoopingStmt(cond func() string, body *ast.BlockStmt, bodyPrefix, post func(), label *types.Label, flatten bool) {
prevFlowData := fc.flowDatas[nil]
data := &flowData{
postStmt: post,
}
if flatten {
data.beginCase = fc.caseCounter
data.endCase = fc.caseCounter + 1
fc.caseCounter += 2
}
fc.flowDatas[nil] = data
fc.flowDatas[label] = data
defer func() {
delete(fc.flowDatas, label)
fc.flowDatas[nil] = prevFlowData
}()
if !flatten && label != nil {
fc.Printf("%s:", label.Name())
}
isTerminated := false
fc.PrintCond(!flatten, "while (true) {", fmt.Sprintf("case %d:", data.beginCase))
fc.Indented(func() {
condStr := cond()
if condStr != "true" {
fc.PrintCond(!flatten, fmt.Sprintf("if (!(%s)) { break; }", condStr), fmt.Sprintf("if(!(%s)) { $s = %d; continue; }", condStr, data.endCase))
}
prevEV := fc.pkgCtx.escapingVars
fc.handleEscapingVars(body)
if bodyPrefix != nil {
bodyPrefix()
}
fc.translateStmtList(body.List)
if len(body.List) != 0 {
switch body.List[len(body.List)-1].(type) {
case *ast.ReturnStmt, *ast.BranchStmt:
isTerminated = true
}
}
if !isTerminated {
post()
}
fc.pkgCtx.escapingVars = prevEV
})
if flatten {
// If the last statement of the loop is a return or unconditional branching
// statement, there's no need for an instruction to go back to the beginning
// of the loop.
if !isTerminated {
fc.Printf("$s = %d; continue;", data.beginCase)
}
fc.Printf("case %d:", data.endCase)
} else {
fc.Printf("}")
}
}
func (fc *funcContext) translateAssign(lhs, rhs ast.Expr, define bool) string {
lhs = astutil.RemoveParens(lhs)
if isBlank(lhs) {
panic("translateAssign with blank lhs")
}
if l, ok := lhs.(*ast.IndexExpr); ok {
if t, ok := fc.typeOf(l.X).Underlying().(*types.Map); ok {
if typesutil.IsJsObject(fc.typeOf(l.Index)) {
fc.pkgCtx.errList = append(fc.pkgCtx.errList, types.Error{Fset: fc.pkgCtx.fileSet, Pos: l.Index.Pos(), Msg: "cannot use js.Object as map key"})
}
keyVar := fc.newLocalVariable("_key")
return fmt.Sprintf(
`%s = %s; (%s || $throwRuntimeError("assignment to entry in nil map")).set(%s.keyFor(%s), { k: %s, v: %s });`,
keyVar,
fc.translateImplicitConversionWithCloning(l.Index, t.Key()),
fc.translateExpr(l.X),
fc.typeName(t.Key()),
keyVar,
keyVar,
fc.translateImplicitConversionWithCloning(rhs, t.Elem()),
)
}
}
lhsType := fc.typeOf(lhs)
rhsExpr := fc.translateConversion(rhs, lhsType)
if _, ok := rhs.(*ast.CompositeLit); ok && define {
return fmt.Sprintf("%s = %s;", fc.translateExpr(lhs), rhsExpr) // skip $copy
}
isReflectValue := false
if named, ok := lhsType.(*types.Named); ok && named.Obj().Pkg() != nil && named.Obj().Pkg().Path() == "reflect" && named.Obj().Name() == "Value" {
isReflectValue = true
}
if !isReflectValue { // this is a performance hack, but it is safe since reflect.Value has no exported fields and the reflect package does not violate this assumption
switch lhsType.Underlying().(type) {
case *types.Array, *types.Struct:
if define {
return fmt.Sprintf("%s = $clone(%s, %s);", fc.translateExpr(lhs), rhsExpr, fc.typeName(lhsType))
}
return fmt.Sprintf("%s.copy(%s, %s);", fc.typeName(lhsType), fc.translateExpr(lhs), rhsExpr)
}
}
switch l := lhs.(type) {
case *ast.Ident:
return fmt.Sprintf("%s = %s;", fc.objectName(fc.pkgCtx.ObjectOf(l)), rhsExpr)
case *ast.SelectorExpr:
sel, ok := fc.selectionOf(l)
if !ok {
// qualified identifier
return fmt.Sprintf("%s = %s;", fc.objectName(fc.pkgCtx.Uses[l.Sel]), rhsExpr)
}
fields, jsTag := fc.translateSelection(sel, l.Pos())
if jsTag != "" {
return fmt.Sprintf("%s.%s%s = %s;", fc.translateExpr(l.X), strings.Join(fields, "."), formatJSStructTagVal(jsTag), fc.externalize(rhsExpr.String(), sel.Type()))
}
return fmt.Sprintf("%s.%s = %s;", fc.translateExpr(l.X), strings.Join(fields, "."), rhsExpr)
case *ast.StarExpr:
return fmt.Sprintf("%s.$set(%s);", fc.translateExpr(l.X), rhsExpr)
case *ast.IndexExpr:
switch t := fc.typeOf(l.X).Underlying().(type) {
case *types.Array, *types.Pointer:
pattern := rangeCheck("%1e[%2f] = %3s", fc.pkgCtx.Types[l.Index].Value != nil, true)
if _, ok := t.(*types.Pointer); ok { // check pointer for nil (attribute getter causes a panic)
pattern = `%1e.nilCheck, ` + pattern
}
return fc.formatExpr(pattern, l.X, l.Index, rhsExpr).String() + ";"
case *types.Slice:
return fc.formatExpr(rangeCheck("%1e.$array[%1e.$offset + %2f] = %3s", fc.pkgCtx.Types[l.Index].Value != nil, false), l.X, l.Index, rhsExpr).String() + ";"
default:
panic(fmt.Sprintf("Unhandled lhs type: %T\n", t))
}
default:
panic(fmt.Sprintf("Unhandled lhs type: %T\n", l))
}
}
func (fc *funcContext) translateResults(results []ast.Expr) string {
tuple := fc.typeResolver.Substitute(fc.sig.Sig.Results()).(*types.Tuple)
switch tuple.Len() {
case 0:
return ""
case 1:
result := fc.zeroValue(tuple.At(0).Type())
if results != nil {
result = results[0]
}
v := fc.translateImplicitConversion(result, tuple.At(0).Type())
fc.delayedOutput = nil
return " " + v.String()
default:
if len(results) == 1 {
resultTuple := fc.typeOf(results[0]).(*types.Tuple)
if resultTuple.Len() != tuple.Len() {
panic("invalid tuple return assignment")
}
resultExpr := fc.translateExpr(results[0]).String()
if types.Identical(resultTuple, tuple) {
return " " + resultExpr
}
tmpVar := fc.newLocalVariable("_returncast")
fc.Printf("%s = %s;", tmpVar, resultExpr)
// Not all the return types matched, map everything out for implicit casting
results = make([]ast.Expr, resultTuple.Len())
for i := range results {
results[i] = fc.newIdent(fmt.Sprintf("%s[%d]", tmpVar, i), resultTuple.At(i).Type())
}
}
values := make([]string, tuple.Len())
for i := range values {
result := fc.zeroValue(tuple.At(i).Type())
if results != nil {
result = results[i]
}
values[i] = fc.translateImplicitConversion(result, tuple.At(i).Type()).String()
}
fc.delayedOutput = nil
return " [" + strings.Join(values, ", ") + "]"
}
}
func (fc *funcContext) labelCase(label *types.Label) int {
labelCase, ok := fc.labelCases[label]
if !ok {
labelCase = fc.caseCounter
fc.caseCounter++
fc.labelCases[label] = labelCase
}
return labelCase
}