protected override ICode VisitSwitch(StmtSwitch s)
 {
     this.NewLine();
     this.js.Append("switch (");
     this.Visit(s.Expr);
     this.js.Append(") {");
     foreach (var @case in s.Cases)
     {
         this.NewLine();
         this.js.AppendFormat("case {0}:", @case.Value);
         this.indent++;
         this.Visit(@case.Stmt);
         this.indent--;
     }
     if (s.Default != null)
     {
         this.NewLine();
         this.js.Append("default:");
         this.indent++;
         this.Visit(s.Default);
         this.indent--;
     }
     this.NewLine();
     this.js.Append("}");
     return(s);
 }
        protected virtual ICode VisitSwitch(StmtSwitch s)
        {
            this.ThrowOnNoOverride();
            var expr     = (Expr)this.Visit(s.Expr);
            var @default = (Stmt)this.Visit(s.Default);
            List <StmtSwitch.Case> cases = null;

            foreach (var @case in s.Cases)
            {
                var c = (Stmt)this.Visit(@case.Stmt);
                if (c != @case.Stmt && cases == null)
                {
                    cases = new List <StmtSwitch.Case>(s.Cases.TakeWhile(x => x != @case));
                }
                if (cases != null)
                {
                    cases.Add(new StmtSwitch.Case(@case.Value, c));
                }
            }
            if (cases != null || expr != s.Expr || @default != s.Default)
            {
                return(new StmtSwitch(s.Ctx, expr, cases ?? s.Cases, @default));
            }
            else
            {
                return(s);
            }
        }
Beispiel #3
0
        private void BuildBlock(IEnumerable <Instruction> insts, Instruction startOfNextPart)
        {
            var inst0 = insts.First();
            var instN = insts.Last();
            // Any instruction in the method could target this block
            var blockStarts = this.methodBlockStarts
                              .Where(x => x.Offset >= inst0.Offset && x.Offset <= instN.Offset)
                              .ToArray();

            // For each block create a StmtCil, with the correct ending.
            // Endings that require a expression have them set to null; they will be filled in during CIL decoding
            for (int i = 0; i < blockStarts.Length; i++)
            {
                var  start      = blockStarts[i];
                var  end        = i == blockStarts.Length - 1 ? insts.Last() : blockStarts[i + 1].Previous;
                var  blockInsts = start.GetRange(end);
                Stmt blockEndStmt;
                var  code = end.OpCode.Code;
                switch (end.OpCode.FlowControl)
                {
                case FlowControl.Cond_Branch:
                    if (code == Code.Switch)
                    {
                        var cases = ((Instruction[])end.Operand).Select(x => new StmtContinuation(this.ctx, x, false)).ToArray();
                        foreach (var @case in cases)
                        {
                            this.mappable.Add(@case);
                        }
                        var @default = new StmtContinuation(this.ctx, end.Next, false);
                        this.mappable.Add(@default);
                        blockEndStmt = new StmtSwitch(this.ctx, new ExprVarInstResult(this.ctx, end, this.ctx.Int32),
                                                      cases.Select((x, value) => new StmtSwitch.Case(value, x)).ToArray(),
                                                      @default);
                    }
                    else
                    {
                        var ifTrue  = new StmtContinuation(this.ctx, (Instruction)end.Operand, false);
                        var ifFalse = new StmtContinuation(this.ctx, end.Next, false);
                        this.mappable.Add(ifTrue);
                        this.mappable.Add(ifFalse);
                        blockEndStmt = new StmtIf(this.ctx, new ExprVarInstResult(this.ctx, end, this.ctx.Boolean), ifTrue, ifFalse);
                    }
                    break;

                case FlowControl.Branch:
                    var leaveProtectedRegion = code == Code.Leave || code == Code.Leave_S;
                    blockEndStmt = new StmtContinuation(this.ctx, (Instruction)end.Operand, leaveProtectedRegion);
                    this.mappable.Add((StmtContinuation)blockEndStmt);
                    break;

                case FlowControl.Next:
                case FlowControl.Call:
                    blockEndStmt = new StmtContinuation(this.ctx, end.Next, false);
                    this.mappable.Add((StmtContinuation)blockEndStmt);
                    break;

                case FlowControl.Return:
                    switch (code)
                    {
                    case Code.Endfinally:
                        blockEndStmt = new StmtContinuation(this.ctx, startOfNextPart, true);
                        this.mappable.Add((StmtContinuation)blockEndStmt);
                        break;

                    case Code.Ret:
                        blockEndStmt = new StmtContinuation(this.ctx, this.endBlock, false);
                        blockInsts   = start == end?Enumerable.Empty <Instruction>() : start.GetRange(end.Previous); // Remove 'ret' from statements

                        break;

                    default:
                        blockEndStmt = null;
                        break;
                    }
                    break;

                case FlowControl.Throw:
                    blockEndStmt = null;
                    break;

                default:
                    throw new NotImplementedException("Cannot handle: " + end.OpCode.FlowControl);
                }
                var block = new StmtCil(this.ctx, blockInsts, blockEndStmt);
                this.blockMap.Add(start, new List <Stmt> {
                    block
                });
            }
        }
        protected override ICode VisitSwitch(StmtSwitch s)
        {
            // If switch statement contains no continuations then it doesn't need processing
            if (!VisitorFindContinuations.Any(s))
            {
                return(base.VisitSwitch(s));
            }
            var ctx = s.Ctx;

            // If any cases go to the same continuation as the default case, remove them
            if (s.Default != null && s.Default.StmtType == Stmt.NodeType.Continuation)
            {
                var defaultCont   = (StmtContinuation)s.Default;
                var sameAsDefault = s.Cases
                                    .Where(x => x.Stmt != null && x.Stmt.StmtType == Stmt.NodeType.Continuation && ((StmtContinuation)x.Stmt).To == defaultCont.To)
                                    .ToArray();
                if (sameAsDefault.Any())
                {
                    var cases = s.Cases.Except(sameAsDefault);
                    return(new StmtSwitch(ctx, s.Expr, cases, s.Default));
                }
            }
            // If multiple case statements all go the same continuation, then put them consecutively
            var groupedByTo = s.Cases
                              .Where(x => x.Stmt != null && x.Stmt.StmtType == Stmt.NodeType.Continuation)
                              .GroupBy(x => ((StmtContinuation)x.Stmt).To)
                              .Where(x => x.Count() >= 2)
                              .ToArray();

            if (groupedByTo.Any())
            {
                var cases         = s.Cases.Except(groupedByTo.SelectMany(x => x));
                var combinedCases = groupedByTo.SelectMany(x => {
                    var same      = x.ToArray();
                    var last      = same.Last();
                    var sameCases = same.Take(same.Length - 1).Select(y => new StmtSwitch.Case(y.Value, null))
                                    .Concat(new StmtSwitch.Case(last.Value, last.Stmt));
                    return(sameCases);
                });
                var allCases = cases.Concat(combinedCases).ToArray();
                return(new StmtSwitch(ctx, s.Expr, allCases, s.Default));
            }
            Func <Stmt, IEnumerable <StmtContinuation> > getSingleFinalContinuation = stmt => {
                if (stmt == null)
                {
                    return(Enumerable.Empty <StmtContinuation>());
                }
                var contCount = VisitorFindContinuations.Get(stmt).Count();
                if (contCount == 0)
                {
                    // Case contains return or throw
                    return(Enumerable.Empty <StmtContinuation>());
                }
                if (contCount == 1)
                {
                    if (stmt.StmtType == Stmt.NodeType.Continuation)
                    {
                        return(new[] { (StmtContinuation)stmt });
                    }
                    if (stmt.StmtType == Stmt.NodeType.Block)
                    {
                        var stmtBlock = (StmtBlock)stmt;
                        var last      = stmtBlock.Statements.LastOrDefault();
                        if (last != null && last.StmtType == Stmt.NodeType.Continuation)
                        {
                            return(new[] { (StmtContinuation)last });
                        }
                    }
                }
                return(new StmtContinuation[] { null });
            };
            var conts = s.Cases.Select(x => x.Stmt).Concat(s.Default).SelectMany(x => getSingleFinalContinuation(x)).ToArray();

            if (conts.All(x => x != null))
            {
                // If all cases end with a continuation to the same stmt, then put that stmt after the switch and remove all continuations
                if (conts.AllSame(x => x.To))
                {
                    Func <Stmt, Stmt> removeCont = stmt => {
                        if (stmt == null)
                        {
                            return(null);
                        }
                        switch (stmt.StmtType)
                        {
                        case Stmt.NodeType.Continuation:
                            return(new StmtBreak(ctx));

                        case Stmt.NodeType.Block:
                            var sBlock = (StmtBlock)stmt;
                            var stmts  = sBlock.Statements.ToArray();
                            if (stmts.Last().StmtType == Stmt.NodeType.Continuation)
                            {
                                stmts = stmts.Take(stmts.Length - 1).Concat(new StmtBreak(ctx)).ToArray();
                                return(new StmtBlock(ctx, stmts));
                            }
                            else
                            {
                                return(stmt);
                            }

                        default:
                            return(stmt);
                        }
                    };
                    var cases   = s.Cases.Select(x => new StmtSwitch.Case(x.Value, removeCont(x.Stmt))).ToArray();
                    var @switch = new StmtSwitch(ctx, s.Expr, cases, removeCont(s.Default));
                    return(new StmtBlock(ctx, @switch, conts[0]));
                }
                else if (this.lastChance)
                {
                    // HACK: Change it into multiple if statements
                    var multiValues = new List <int>();
                    var converted   = s.Cases.Aggregate(s.Default, (@else, @case) => {
                        multiValues.Add(@case.Value);
                        if (@case.Stmt == null)
                        {
                            return(@else);
                        }
                        else
                        {
                            var cond = multiValues.Aggregate((Expr)ctx.Literal(false), (expr, caseValue) => {
                                return(ctx.ExprGen.Or(expr, ctx.ExprGen.Equal(s.Expr, ctx.Literal(caseValue))));
                            });
                            multiValues.Clear();
                            var @if = new StmtIf(ctx, cond, @case.Stmt, @else);
                            return(@if);
                        }
                    });
                    return(converted);
                }
                // If some cases end in a continuation that itself ends in a continuation that other cases end with
                // then use an extra variable to store whether to execute the intermediate code
                // TODO: This is too specific, need a more general-purpose solution to the problem where cases
                // don't all end by going to the same place
                //var contTos = conts.Select(x => x.To).Distinct().ToArray();
                //var finalContTos = contTos.Select(x => getSingleFinalContinuation(x).Select(y => y.NullThru(z => z.To))).SelectMany(x => x).ToArray();
                //if (!finalContTos.Any(x => x == null)) {
                //    // All continuations are fully substituted
                //    var distinctFinalContTos = finalContTos.Distinct().ToArray();
                //    if (distinctFinalContTos.Length == 1) {
                //        var selector = ctx.Local(ctx.Int32);
                //        var inIfCont = contTos.Single(x => x != distinctFinalContTos[0]);
                //        var inIf = new StmtContinuation(ctx, inIfCont, false);
                //        var afterIf = new StmtContinuation(ctx, distinctFinalContTos[0], false);
                //        var allCasesTo = new StmtBlock(ctx,
                //            new StmtIf(ctx, ctx.ExprGen.Equal(selector, ctx.Literal(1)), inIf, null),
                //            afterIf);
                //        Func<Stmt, Stmt> adjustCont = stmt => {
                //            var cont = VisitorFindContinuations.Get(stmt).Single();
                //            var newCont = new StmtContinuation(ctx, allCasesTo, false);
                //            var contChanged = (Stmt)VisitorReplace.V(stmt, cont, newCont);
                //            var sValue = cont.To == inIf.To ? 1 : 0;
                //            var withSelectorSet = new StmtBlock(ctx,
                //                new StmtAssignment(ctx, selector, ctx.Literal(sValue)),
                //                contChanged);
                //            return withSelectorSet;
                //        };
                //        var cases = s.Cases.Select(x => new StmtSwitch.Case(x.Value, adjustCont(x.Stmt))).ToArray();
                //        var @switch = new StmtSwitch(ctx, s.Expr, cases, adjustCont(s.Default));
                //        return @switch;
                //    } else {
                //        throw new NotImplementedException();
                //    }
                //}
            }

            return(base.VisitSwitch(s));
        }