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); } }
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)); }