From 9cd497340972ec3924049add293185630e3fcb5c Mon Sep 17 00:00:00 2001 From: kamphaus Date: Tue, 12 Jun 2018 03:28:45 +0200 Subject: [PATCH] Support switch label clauses. Fixes #693 (#694) --- tests/code_quality/for.expected.c | 2 +- tests/code_quality/if.expected.c | 2 +- tests/code_quality/operators.expected.c | 2 +- tests/code_quality/switch.expected.c | 6 +- tests/switch.c | 100 ++++++++- transpiler/switch.go | 267 ++++++++++++++++++++++-- 6 files changed, 352 insertions(+), 27 deletions(-) diff --git a/tests/code_quality/for.expected.c b/tests/code_quality/for.expected.c index e47929222..5f6808746 100644 --- a/tests/code_quality/for.expected.c +++ b/tests/code_quality/for.expected.c @@ -1,5 +1,5 @@ /* - Package main - transpiled by c2go version: v0.22.4 Aluminium 2018-04-24 + Package main - transpiled by c2go version: v0.23.0 Berkelium 2018-04-27 If you have found any issues, please raise an issue at: https://github.com/elliotchance/c2go/ diff --git a/tests/code_quality/if.expected.c b/tests/code_quality/if.expected.c index 47c64a923..76be90723 100644 --- a/tests/code_quality/if.expected.c +++ b/tests/code_quality/if.expected.c @@ -1,5 +1,5 @@ /* - Package main - transpiled by c2go version: v0.22.4 Aluminium 2018-04-24 + Package main - transpiled by c2go version: v0.23.0 Berkelium 2018-04-27 If you have found any issues, please raise an issue at: https://github.com/elliotchance/c2go/ diff --git a/tests/code_quality/operators.expected.c b/tests/code_quality/operators.expected.c index 84520a95c..4e57f2c37 100644 --- a/tests/code_quality/operators.expected.c +++ b/tests/code_quality/operators.expected.c @@ -1,5 +1,5 @@ /* - Package main - transpiled by c2go version: v0.22.4 Aluminium 2018-04-24 + Package main - transpiled by c2go version: v0.23.0 Berkelium 2018-04-27 If you have found any issues, please raise an issue at: https://github.com/elliotchance/c2go/ diff --git a/tests/code_quality/switch.expected.c b/tests/code_quality/switch.expected.c index df8d9295b..e8a0dc18a 100644 --- a/tests/code_quality/switch.expected.c +++ b/tests/code_quality/switch.expected.c @@ -1,5 +1,5 @@ /* - Package main - transpiled by c2go version: v0.22.4 Aluminium 2018-04-24 + Package main - transpiled by c2go version: v0.23.0 Berkelium 2018-04-27 If you have found any issues, please raise an issue at: https://github.com/elliotchance/c2go/ @@ -28,7 +28,11 @@ func switch_function() { return } case int32(4): + { + } case int32(5): + { + } case int32(6): fallthrough case int32(7): diff --git a/tests/switch.c b/tests/switch.c index ad5691295..ae7cf1e2f 100644 --- a/tests/switch.c +++ b/tests/switch.c @@ -11,6 +11,7 @@ // CompoundStmt with 12 children. #include +#include #include "tests.h" void match_a_single_case() @@ -125,6 +126,45 @@ void fallthrough_several_midway_default() } } +void goto_label(bool use_goto) +{ + for (;;) { + switch (0) + { + case 3: + continue; + case 0: + if (use_goto) { + for (;;) + break; + goto LABEL; + fail("code should not reach here"); + } else if (false) { + goto LABELX; + goto LABELY; + fail("code should not reach here"); + } + /* other comment */ + // some comment + /* fallthrough */ + LABELY: + case 4: + LABEL: + printf("x"); + case 1: + pass(__func__); + break; + case 2: + ; + LABELX: + default: + fail("code should not reach here"); + break; + } + break; + } +} + void scoped_match_a_single_case() { switch (1) @@ -278,6 +318,60 @@ void scoped_fallthrough_several_midway_default() } } +void scoped_goto_label(bool use_goto) +{ + for (;;) { + switch (0) + { + case 3: + { + continue; + } + case 0: + { + if (use_goto) { + for (;;) { + break; + } + goto LABEL; + fail("code should not reach here"); + } else if (false) { + goto LABELX; + goto LABELY; + fail("code should not reach here"); + } + /* other comment */ + // some comment + /* fallthrough */ + } + LABELY: {} + case 4: {} + LABEL: + { + printf("x"); + } + case 1: + { + pass(__func__); + break; + } + case 2: + { + int x = 0; + printf("%d", x); + break; + } + LABELX: {} + default: + { + fail("code should not reach here"); + break; + } + } + break; + } +} + typedef struct I67 I67; struct I67{ int x,y; @@ -376,7 +470,7 @@ void switch_without_input() int main() { - plan(33); + plan(37); match_a_single_case(); fallthrough_to_next_case(); @@ -384,6 +478,8 @@ int main() match_default(); fallthrough_several_cases_including_default(); fallthrough_several_midway_default(); + goto_label(false); + goto_label(true); // For each of the tests above there will be identical cases that use scopes // for the case statements. @@ -393,6 +489,8 @@ int main() scoped_match_default(); scoped_fallthrough_several_cases_including_default(); scoped_fallthrough_several_midway_default(); + scoped_goto_label(false); + scoped_goto_label(true); switch_issue67(); empty_switch(); diff --git a/transpiler/switch.go b/transpiler/switch.go index 2fe0e01ea..85ce881e0 100644 --- a/transpiler/switch.go +++ b/transpiler/switch.go @@ -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) ( @@ -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 { - // Do not consider a node which is not a case or default statement here + ls, ok3 := cn.(*ast.LabelStmt) + if !ok1 && !ok2 && !ok3 || cn == nil || len(cn.Children()) == 0 { + // Do not consider a node which is not a case, label 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:]) @@ -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] + } } } } @@ -97,13 +103,25 @@ func transpileSwitchStmt(n *ast.SwitchStmt, p *program.Program) ( v.Children()[len(v.Children())-1] = &compoundStmt } } + // For simplification - each LabelStmt will have CompoundStmt + if v, ok := body.Children()[i].(*ast.LabelStmt); ok { + if _, ok := v.Children()[len(v.Children())-1].(*ast.CompoundStmt); !ok { + var compoundStmt ast.CompoundStmt + compoundStmt.AddChild(v.Children()[len(v.Children())-1]) + v.Children()[len(v.Children())-1] = &compoundStmt + } + } } + 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() @@ -155,7 +173,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 } @@ -165,7 +187,7 @@ func transpileSwitchStmt(n *ast.SwitchStmt, p *program.Program) ( } if !isFallThrough { if len(body) > 1 { - cases[i].Body = body + cs.Body = body } continue } @@ -174,17 +196,18 @@ 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]) + v.List = v.List[:len(v.List)-1] + cs.Body = body[:len(body)-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]} } } } @@ -200,6 +223,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{ @@ -209,7 +237,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 { @@ -254,16 +282,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 @@ -276,7 +304,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) @@ -287,20 +315,39 @@ 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 { + ft := &goast.BranchStmt{ + Tok: token.FALLTHROUGH, + } + if _, ok2 := ls.Stmt.(*goast.EmptyStmt); ok2 { + ls.Stmt = ft + } else if bs, ok2 := ls.Stmt.(*goast.BlockStmt); ok2 { + bs.List = append(bs.List, ft) + } else { + ls.Stmt = &goast.BlockStmt{ + List: []goast.Stmt{ + ls.Stmt, + ft, + }, + } + } + } } caseEndedWithBreak = false - var singleCase *goast.CaseClause + var singleCase goast.Stmt var err error var newPre []goast.Stmt var newPost []goast.Stmt @@ -311,6 +358,21 @@ func appendCaseOrDefaultToNormalizedCases(cases []*goast.CaseClause, case *ast.DefaultStmt: singleCase, err = transpileDefaultStmt(c, p) + + case *ast.LabelStmt: + singleCase, newPre, newPost, err = transpileLabelStmt(c, p) + lc, ok := singleCase.(*goast.LabeledStmt) + if !ok { + panic("expected *goast.LabeledStmt") + } + if len(newPost) == 1 { + lc.Stmt = newPost[0] + } else if len(newPost) > 1 { + lc.Stmt = &goast.BlockStmt{ + List: newPost, + } + } + newPost = []goast.Stmt{} } if singleCase != nil { @@ -320,7 +382,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 @@ -360,3 +422,164 @@ 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, + }) + funcTransformBreak := 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, + }) + } + } + if _, ok := node.(*goast.ForStmt); ok { + return false + } + if _, ok := node.(*goast.RangeStmt); ok { + return false + } + if _, ok := node.(*goast.SwitchStmt); ok { + return false + } + if _, ok := node.(*goast.TypeSwitchStmt); ok { + return false + } + if _, ok := node.(*goast.SelectStmt); ok { + return false + } + return true + } + 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, funcTransformBreak, nil) + 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: + var isFallThrough bool + // Remove fallthrough if it's the only statement + if v, ok := c.Stmt.(*goast.BranchStmt); ok { + if v.Tok == token.FALLTHROUGH { + c.Stmt = &goast.EmptyStmt{} + isFallThrough = true + } + } else if b, ok := c.Stmt.(*goast.BlockStmt); ok { + // Remove fallthrough if LabeledStmt contains a BlockStmt + if v, ok := b.List[len(b.List)-1].(*goast.BranchStmt); ok { + if v.Tok == token.FALLTHROUGH { + b.List = b.List[:len(b.List)-1] + isFallThrough = true + } + } + } + + // Replace break's with goto swEndLabel + astutil.Apply(c, funcTransformBreak, nil) + + // append label followed by label body + postStmts = append(postStmts, c) + + // 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, + }) + } + } + } + postStmts = append(postStmts, &goast.LabeledStmt{ + Label: util.NewIdent(swEndLabel), + Stmt: &goast.EmptyStmt{}, + }) + return +}