Skip to content

Commit

Permalink
Transpile switch label clause. Fixes elliotchance#693
Browse files Browse the repository at this point in the history
  • Loading branch information
kamphaus authored and christophe-kamphaus-jemmic committed May 14, 2018
1 parent b16b421 commit 7df3076
Show file tree
Hide file tree
Showing 8 changed files with 4,016 additions and 21 deletions.
188 changes: 167 additions & 21 deletions transpiler/switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
package transpiler

import (
"fmt"
goast "go/ast"
"go/token"

"fmt"

"github.com/elliotchance/c2go/ast"
"github.com/elliotchance/c2go/program"
"github.com/elliotchance/c2go/util"
"golang.org/x/tools/go/ast/astutil"
)

func transpileSwitchStmt(n *ast.SwitchStmt, p *program.Program) (
Expand Down Expand Up @@ -50,14 +51,16 @@ func transpileSwitchStmt(n *ast.SwitchStmt, p *program.Program) (
cn := body.ChildNodes[i]
cs, ok1 := cn.(*ast.CaseStmt)
ds, ok2 := cn.(*ast.DefaultStmt)
if !ok1 && !ok2 {
ls, ok3 := cn.(*ast.LabelStmt)
if !ok1 && !ok2 && !ok3 {
// Do not consider a node which is not a case or default statement here
continue
}
lastCn := cn.Children()[len(cn.Children())-1]
_, isCase := lastCn.(*ast.CaseStmt)
_, isDefault := lastCn.(*ast.DefaultStmt)
if isCase || isDefault {
_, isLabel := lastCn.(*ast.LabelStmt)
if isCase || isDefault || isLabel {
// Insert lastCn before next case in body (https://github.com/golang/go/wiki/SliceTricks)
body.ChildNodes = append(body.ChildNodes, &ast.CompoundStmt{})
copy(body.ChildNodes[i+2:], body.ChildNodes[i+1:])
Expand All @@ -76,6 +79,9 @@ func transpileSwitchStmt(n *ast.SwitchStmt, p *program.Program) (
if ok2 {
ds.ChildNodes = ds.ChildNodes[:len(ds.ChildNodes)-1]
}
if ok3 {
ls.ChildNodes = ls.ChildNodes[:len(ls.ChildNodes)-1]
}
}
}
}
Expand All @@ -99,11 +105,15 @@ func transpileSwitchStmt(n *ast.SwitchStmt, p *program.Program) (
}
}

hasLabelCase := false
// Move element inside CompoundStmt
for i := 0; i < len(body.Children()); i++ {
switch body.Children()[i].(type) {
case *ast.CaseStmt, *ast.DefaultStmt:
// do nothing
case *ast.LabelStmt:
hasLabelCase = true
// do nothing else
default:
if i != 0 {
lastStmt := body.Children()[i-1].Children()
Expand Down Expand Up @@ -155,7 +165,11 @@ func transpileSwitchStmt(n *ast.SwitchStmt, p *program.Program) (
// return
//
for i := range cases {
body := cases[i].Body
cs, ok := cases[i].(*goast.CaseClause)
if !ok {
continue
}
body := cs.Body
if len(body) != 2 {
continue
}
Expand All @@ -165,7 +179,7 @@ func transpileSwitchStmt(n *ast.SwitchStmt, p *program.Program) (
}
if !isFallThrough {
if len(body) > 1 {
cases[i].Body = body
cs.Body = body
}
continue
}
Expand All @@ -174,17 +188,17 @@ func transpileSwitchStmt(n *ast.SwitchStmt, p *program.Program) (
if vv, ok := v.List[len(v.List)-1].(*goast.BranchStmt); ok {
if vv.Tok == token.BREAK {
if isFallThrough {
cases[i].Body = append(v.List[:len(v.List)-1])
cs.Body = append(v.List[:len(v.List)-1])
continue
}
}
}
if _, ok := v.List[len(v.List)-1].(*goast.ReturnStmt); ok {
cases[i].Body = body[:len(body)-1]
cs.Body = body[:len(body)-1]
continue
}
} else {
cases[i].Body = []goast.Stmt{body[1]}
cs.Body = []goast.Stmt{body[1]}
}
}
}
Expand All @@ -200,6 +214,11 @@ func transpileSwitchStmt(n *ast.SwitchStmt, p *program.Program) (
stmts = append(stmts, singleCase)
}

if hasLabelCase {
stmts, newPost = handleLabelCases(cases, p)
preStmts, postStmts = combinePreAndPostStmts(preStmts, newPost, []goast.Stmt{}, postStmts)
}

return &goast.SwitchStmt{
Tag: condition,
Body: &goast.BlockStmt{
Expand All @@ -209,7 +228,7 @@ func transpileSwitchStmt(n *ast.SwitchStmt, p *program.Program) (
}

func normalizeSwitchCases(body *ast.CompoundStmt, p *program.Program) (
_ []*goast.CaseClause, preStmts []goast.Stmt, postStmts []goast.Stmt, err error) {
_ []goast.Stmt, preStmts []goast.Stmt, postStmts []goast.Stmt, err error) {
// The body of a switch has a non uniform structure. For example:
//
// switch a {
Expand Down Expand Up @@ -254,16 +273,16 @@ func normalizeSwitchCases(body *ast.CompoundStmt, p *program.Program) (
//
// During this translation we also remove 'break' or append a 'fallthrough'.

cases := []*goast.CaseClause{}
cases := []goast.Stmt{}
caseEndedWithBreak := false

for _, x := range body.Children() {
switch c := x.(type) {
case *ast.CaseStmt, *ast.DefaultStmt:
case *ast.CaseStmt, *ast.DefaultStmt, *ast.LabelStmt:
var newPre, newPost []goast.Stmt
cases, newPre, newPost, err = appendCaseOrDefaultToNormalizedCases(cases, c, caseEndedWithBreak, p)
if err != nil {
return []*goast.CaseClause{}, nil, nil, err
return []goast.Stmt{}, nil, nil, err
}
caseEndedWithBreak = false

Expand All @@ -276,7 +295,7 @@ func normalizeSwitchCases(body *ast.CompoundStmt, p *program.Program) (
var newPre, newPost []goast.Stmt
stmt, newPre, newPost, err = transpileToStmt(x, p)
if err != nil {
return []*goast.CaseClause{}, nil, nil, err
return []goast.Stmt{}, nil, nil, err
}
preStmts = append(preStmts, newPre...)
preStmts = append(preStmts, stmt)
Expand All @@ -287,20 +306,27 @@ func normalizeSwitchCases(body *ast.CompoundStmt, p *program.Program) (
return cases, preStmts, postStmts, nil
}

func appendCaseOrDefaultToNormalizedCases(cases []*goast.CaseClause,
func appendCaseOrDefaultToNormalizedCases(cases []goast.Stmt,
stmt ast.Node, caseEndedWithBreak bool, p *program.Program) (
[]*goast.CaseClause, []goast.Stmt, []goast.Stmt, error) {
[]goast.Stmt, []goast.Stmt, []goast.Stmt, error) {
preStmts := []goast.Stmt{}
postStmts := []goast.Stmt{}

if len(cases) > 0 && !caseEndedWithBreak {
cases[len(cases)-1].Body = append(cases[len(cases)-1].Body, &goast.BranchStmt{
Tok: token.FALLTHROUGH,
})
if cs, ok := cases[len(cases)-1].(*goast.CaseClause); ok {
cs.Body = append(cs.Body, &goast.BranchStmt{
Tok: token.FALLTHROUGH,
})
}
if ls, ok := cases[len(cases)-1].(*goast.LabeledStmt); ok {
ls.Stmt = &goast.BranchStmt{
Tok: token.FALLTHROUGH,
}
}
}
caseEndedWithBreak = false

var singleCase *goast.CaseClause
var singleCase goast.Stmt
var err error
var newPre []goast.Stmt
var newPost []goast.Stmt
Expand All @@ -311,6 +337,9 @@ func appendCaseOrDefaultToNormalizedCases(cases []*goast.CaseClause,

case *ast.DefaultStmt:
singleCase, err = transpileDefaultStmt(c, p)

case *ast.LabelStmt:
singleCase, newPre, newPost, err = transpileLabelStmt(c, p)
}

if singleCase != nil {
Expand All @@ -320,7 +349,7 @@ func appendCaseOrDefaultToNormalizedCases(cases []*goast.CaseClause,
preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

if err != nil {
return []*goast.CaseClause{}, nil, nil, err
return []goast.Stmt{}, nil, nil, err
}

return cases, preStmts, postStmts, nil
Expand Down Expand Up @@ -360,3 +389,120 @@ func transpileDefaultStmt(n *ast.DefaultStmt, p *program.Program) (*goast.CaseCl
Body: stmts,
}, nil
}

func handleLabelCases(cases []goast.Stmt, p *program.Program) (newCases []goast.Stmt, postStmts []goast.Stmt) {
// In C a switch can have labels before a case.
// Go does not support this.
// To make it work we translate the switch cases as labels to blocks appended to the switch
// For example:
//
// switch a {
// case 1:
// foo();
// break;
// LABEL:
// case 2:
// bar();
// default:
// baz();
// }
//
// is transpiled as:
//
// switch a {
// case 1:
// goto SW_1_1
// case 2:
// goto SW_1_2
// default:
// goto SW_1_3
// }
// SW_1_1:
// foo()
// goto SW_1_END
// LABEL:
// ;
// SW_1_2:
// bar()
// SW_1_3:
// baz()
// SW_1_END:
// ;
swEndLabel := p.GetNextIdentifier("SW_GENERATED_LABEL_")
postStmts = append(postStmts, &goast.BranchStmt{
Label: util.NewIdent(swEndLabel),
Tok: token.GOTO,
})
for i, x := range cases {
switch c := x.(type) {
case *goast.CaseClause:
caseLabel := p.GetNextIdentifier("SW_GENERATED_LABEL_")

if len(c.Body) == 0 {
c.Body = append(c.Body, &goast.BranchStmt{
Tok: token.BREAK,
})
}
var isFallThrough bool
// Remove fallthrough
if v, ok := c.Body[len(c.Body)-1].(*goast.BranchStmt); ok {
isFallThrough = (v.Tok == token.FALLTHROUGH)
c.Body = c.Body[:len(c.Body)-1]
}
if len(c.Body) == 0 {
c.Body = append(c.Body, &goast.EmptyStmt{})
}

// Replace break's with goto swEndLabel
astutil.Apply(c, nil, func(cursor *astutil.Cursor) bool {
if cursor == nil {
return true
}
node := cursor.Node()
if bs, ok := node.(*goast.BranchStmt); ok {
if bs.Tok == token.BREAK {
cursor.Replace(&goast.BranchStmt{
Label: util.NewIdent(swEndLabel),
Tok: token.GOTO,
})
}
}
return true
})
body := c.Body

// append caseLabel label followed by case body
postStmts = append(postStmts, &goast.LabeledStmt{
Label: util.NewIdent(caseLabel),
Stmt: body[0],
})
body = body[1:]
postStmts = append(postStmts, body...)

// If not last case && no fallthrough goto swEndLabel
if i != len(cases)-1 && !isFallThrough {
postStmts = append(postStmts, &goast.BranchStmt{
Label: util.NewIdent(swEndLabel),
Tok: token.GOTO,
})
}

// In switch case we goto caseLabel
c.Body = []goast.Stmt{
&goast.BranchStmt{
Label: util.NewIdent(caseLabel),
Tok: token.GOTO,
},
}
newCases = append(newCases, c)
case *goast.LabeledStmt:
c.Stmt = &goast.EmptyStmt{}
postStmts = append(postStmts, c)
}
}
postStmts = append(postStmts, &goast.LabeledStmt{
Label: util.NewIdent(swEndLabel),
Stmt: &goast.EmptyStmt{},
})
return
}
Loading

0 comments on commit 7df3076

Please sign in to comment.