mirror of https://github.com/icsharpcode/ILSpy.git
13 changed files with 833 additions and 160 deletions
@ -0,0 +1,297 @@
@@ -0,0 +1,297 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
using System.Text; |
||||
using System.Threading.Tasks; |
||||
|
||||
namespace ICSharpCode.Decompiler.IL.ControlFlow |
||||
{ |
||||
/// <summary>
|
||||
/// C# switch statements are not necessarily compiled into IL switch instructions.
|
||||
/// For example, when the label values are not contiguous, the C# compiler
|
||||
/// will generate if statements similar to a binary search.
|
||||
///
|
||||
/// This class analyses such sequences of if statements to reconstruct the original switch.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This analysis expects to be run on basic blocks (not extended basic blocks).
|
||||
/// </remarks>
|
||||
class SwitchAnalysis |
||||
{ |
||||
/// <summary>
|
||||
/// The variable that is used to represent the switch expression.
|
||||
/// <c>null</c> while analyzing the first block.
|
||||
/// </summary>
|
||||
ILVariable switchVar; |
||||
|
||||
/// <summary>
|
||||
/// The variable to be used as the argument of the switch instruction.
|
||||
/// </summary>
|
||||
public ILVariable SwitchVariable |
||||
{ |
||||
get { return switchVar; } |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets the sections that were detected by the previoous AnalyzeBlock() call.
|
||||
/// </summary>
|
||||
public readonly List<KeyValuePair<LongSet, ILInstruction>> Sections = new List<KeyValuePair<LongSet, ILInstruction>>(); |
||||
|
||||
readonly Dictionary<Block, int> targetBlockToSectionIndex = new Dictionary<Block, int>(); |
||||
|
||||
/// <summary>
|
||||
/// Blocks that can be deleted if the tail of the initial block is replaced with a switch instruction.
|
||||
/// </summary>
|
||||
public readonly List<Block> InnerBlocks = new List<Block>(); |
||||
|
||||
Block rootBlock; |
||||
|
||||
/// <summary>
|
||||
/// Analyze the last two statements in the block and see if they can be turned into a
|
||||
/// switch instruction.
|
||||
/// </summary>
|
||||
/// <returns>true if the block could be analyzed successfully; false otherwise</returns>
|
||||
public bool AnalyzeBlock(Block block) |
||||
{ |
||||
switchVar = null; |
||||
rootBlock = block; |
||||
targetBlockToSectionIndex.Clear(); |
||||
Sections.Clear(); |
||||
InnerBlocks.Clear(); |
||||
return AnalyzeBlock(block, LongSet.Universe, tailOnly: true); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Analyzes the tail end (last two instructions) of a block.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sets <c>switchVar</c> and <c>defaultInstruction</c> if they are null,
|
||||
/// and adds found sections to <c>sectionLabels</c> and <c>sectionInstructions</c>.
|
||||
///
|
||||
/// If the function returns false, <c>sectionLabels</c> and <c>sectionInstructions</c> are unmodified.
|
||||
/// </remarks>
|
||||
/// <param name="block">The block to analyze.</param>
|
||||
/// <param name="inputValues">The possible values of the "interesting" variable
|
||||
/// when control flow reaches this block.</param>
|
||||
/// <param name="tailOnly">If true, analyze only the tail (last two instructions).
|
||||
/// If false, analyze the whole block.</param>
|
||||
bool AnalyzeBlock(Block block, LongSet inputValues, bool tailOnly = false) |
||||
{ |
||||
if (tailOnly) { |
||||
Debug.Assert(block == rootBlock); |
||||
if (block.Instructions.Count < 2) |
||||
return false; |
||||
} else { |
||||
Debug.Assert(switchVar != null); // switchVar should always be determined by the top-level call
|
||||
if (block.IncomingEdgeCount != 1 || block == rootBlock) |
||||
return false; // for now, let's only consider if-structures that form a tree
|
||||
if (block.Instructions.Count != 2) |
||||
return false; |
||||
if (block.Parent != rootBlock.Parent) |
||||
return false; // all blocks should belong to the same container
|
||||
} |
||||
var inst = block.Instructions[block.Instructions.Count - 2]; |
||||
ILInstruction condition, trueInst; |
||||
LongSet trueValues; |
||||
if (inst.MatchIfInstruction(out condition, out trueInst) |
||||
&& AnalyzeCondition(condition, out trueValues) |
||||
) { |
||||
trueValues = trueValues.IntersectWith(inputValues); |
||||
Block trueBlock; |
||||
if (trueInst.MatchBranch(out trueBlock) && AnalyzeBlock(trueBlock, trueValues)) { |
||||
// OK, true block was further analyzed.
|
||||
InnerBlocks.Add(trueBlock); |
||||
} else { |
||||
// Create switch section for trueInst.
|
||||
AddSection(trueValues, trueInst); |
||||
} |
||||
} else if (inst.OpCode == OpCode.SwitchInstruction) { |
||||
if (AnalyzeSwitch((SwitchInstruction)inst, inputValues, out trueValues)) { |
||||
// OK
|
||||
} else { // switch analysis failed (e.g. switchVar mismatch)
|
||||
return false; |
||||
} |
||||
} else { // unknown inst
|
||||
return false; |
||||
} |
||||
|
||||
var remainingValues = inputValues.ExceptWith(trueValues); |
||||
ILInstruction falseInst = block.Instructions.Last(); |
||||
Block falseBlock; |
||||
if (falseInst.MatchBranch(out falseBlock) && AnalyzeBlock(falseBlock, remainingValues)) { |
||||
// OK, false block was further analyzed.
|
||||
InnerBlocks.Add(falseBlock); |
||||
} else { |
||||
// Create switch section for falseInst.
|
||||
AddSection(remainingValues, falseInst); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
private bool AnalyzeSwitch(SwitchInstruction inst, LongSet inputValues, out LongSet anyMatchValues) |
||||
{ |
||||
anyMatchValues = LongSet.Empty; |
||||
long offset; |
||||
if (MatchSwitchVar(inst.Value)) { |
||||
offset = 0; |
||||
} else if (inst.Value.OpCode == OpCode.BinaryNumericInstruction) { |
||||
var bop = (BinaryNumericInstruction)inst.Value; |
||||
if (bop.CheckForOverflow) |
||||
return false; |
||||
long val; |
||||
if (MatchSwitchVar(bop.Left) && MatchLdcI(bop.Right, out val)) { |
||||
switch (bop.Operator) { |
||||
case BinaryNumericOperator.Add: |
||||
offset = -val; |
||||
break; |
||||
case BinaryNumericOperator.Sub: |
||||
offset = val; |
||||
break; |
||||
default: // unknown bop.Operator
|
||||
return false; |
||||
} |
||||
} else { // unknown bop.Left
|
||||
return false; |
||||
} |
||||
} else { // unknown inst.Value
|
||||
return false; |
||||
} |
||||
foreach (var section in inst.Sections) { |
||||
var matchValues = section.Labels.AddOffset(offset).IntersectWith(inputValues); |
||||
AddSection(matchValues, section.Body); |
||||
anyMatchValues = anyMatchValues.UnionWith(matchValues); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Adds a new section to the Sections list.
|
||||
///
|
||||
/// If the instruction is a branch instruction, unify the new section with an existing section
|
||||
/// that also branches to the same target.
|
||||
/// </summary>
|
||||
void AddSection(LongSet values, ILInstruction inst) |
||||
{ |
||||
Block targetBlock; |
||||
if (inst.MatchBranch(out targetBlock)) { |
||||
int index; |
||||
if (targetBlockToSectionIndex.TryGetValue(targetBlock, out index)) { |
||||
Sections[index] = new KeyValuePair<LongSet, ILInstruction>( |
||||
Sections[index].Key.UnionWith(values), |
||||
inst |
||||
); |
||||
} else { |
||||
targetBlockToSectionIndex.Add(targetBlock, Sections.Count); |
||||
Sections.Add(new KeyValuePair<LongSet, ILInstruction>(values, inst)); |
||||
} |
||||
} else { |
||||
Sections.Add(new KeyValuePair<LongSet, ILInstruction>(values, inst)); |
||||
} |
||||
} |
||||
|
||||
bool MatchSwitchVar(ILInstruction inst) |
||||
{ |
||||
if (switchVar != null) |
||||
return inst.MatchLdLoc(switchVar); |
||||
else |
||||
return inst.MatchLdLoc(out switchVar); |
||||
} |
||||
|
||||
bool MatchLdcI(ILInstruction inst, out long val) |
||||
{ |
||||
if (inst.MatchLdcI8(out val)) |
||||
return true; |
||||
int intVal; |
||||
if (inst.MatchLdcI4(out intVal)) { |
||||
val = intVal; |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Analyzes the boolean condition, returning the set of values of the interesting
|
||||
/// variable for which the condition evaluates to true.
|
||||
/// </summary>
|
||||
private bool AnalyzeCondition(ILInstruction condition, out LongSet trueValues) |
||||
{ |
||||
ILInstruction arg; |
||||
Comp comp = condition as Comp; |
||||
long val; |
||||
if (comp != null && MatchSwitchVar(comp.Left) && MatchLdcI(comp.Right, out val)) { |
||||
// if (comp(V OP val))
|
||||
switch (comp.Kind) { |
||||
case ComparisonKind.Equality: |
||||
trueValues = new LongSet(val); |
||||
return true; |
||||
case ComparisonKind.Inequality: |
||||
trueValues = new LongSet(val).Invert(); |
||||
return true; |
||||
case ComparisonKind.LessThan: |
||||
trueValues = MakeGreaterThanOrEqualSet(val, comp.Sign).Invert(); |
||||
return true; |
||||
case ComparisonKind.LessThanOrEqual: |
||||
trueValues = MakeLessThanOrEqualSet(val, comp.Sign); |
||||
return true; |
||||
case ComparisonKind.GreaterThan: |
||||
trueValues = MakeLessThanOrEqualSet(val, comp.Sign).Invert(); |
||||
return true; |
||||
case ComparisonKind.GreaterThanOrEqual: |
||||
trueValues = MakeGreaterThanOrEqualSet(val, comp.Sign); |
||||
return true; |
||||
default: |
||||
trueValues = LongSet.Empty; |
||||
return false; |
||||
} |
||||
} else if (MatchSwitchVar(condition)) { |
||||
// if (ldloc V) --> branch for all values except 0
|
||||
trueValues = new LongSet(0).Invert(); |
||||
return true; |
||||
} else if (condition.MatchLogicNot(out arg)) { |
||||
// if (logic.not(X)) --> branch for all values where if (X) does not branch
|
||||
LongSet falseValues; |
||||
bool res = AnalyzeCondition(arg, out falseValues); |
||||
trueValues = falseValues.Invert(); |
||||
return res; |
||||
} else { |
||||
trueValues = LongSet.Empty; |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
private LongSet MakeGreaterThanOrEqualSet(long val, Sign sign) |
||||
{ |
||||
if (sign == Sign.Signed) { |
||||
return new LongSet(LongInterval.Inclusive(val, long.MaxValue)); |
||||
} else { |
||||
Debug.Assert(sign == Sign.Unsigned); |
||||
if (val >= 0) { |
||||
// The range val to ulong.MaxValue expressed with signed longs
|
||||
// is not a single contiguous range, but two ranges:
|
||||
return new LongSet(LongInterval.Inclusive(val, long.MaxValue)) |
||||
.UnionWith(new LongSet(new LongInterval(long.MinValue, 0))); |
||||
} else { |
||||
return new LongSet(new LongInterval(val, 0)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private LongSet MakeLessThanOrEqualSet(long val, Sign sign) |
||||
{ |
||||
if (sign == Sign.Signed) { |
||||
return new LongSet(LongInterval.Inclusive(long.MinValue, val)); |
||||
} else { |
||||
Debug.Assert(sign == Sign.Unsigned); |
||||
if (val >= 0) { |
||||
return new LongSet(LongInterval.Inclusive(0, val)); |
||||
} else { |
||||
// The range 0 to (ulong)val expressed with signed longs
|
||||
// is not a single contiguous range, but two ranges:
|
||||
return new LongSet(LongInterval.Inclusive(0, long.MaxValue)) |
||||
.UnionWith(new LongSet(LongInterval.Inclusive(long.MinValue, val))); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,87 @@
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) 2016 Daniel Grunwald
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using ICSharpCode.Decompiler.IL.Transforms; |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Text; |
||||
using System.Threading.Tasks; |
||||
using System.Diagnostics; |
||||
|
||||
namespace ICSharpCode.Decompiler.IL.ControlFlow |
||||
{ |
||||
/// <summary>
|
||||
/// C# switch statements are not necessarily compiled into
|
||||
/// IL switch instructions (e.g. when the integer values are non-contiguous).
|
||||
///
|
||||
/// Detect sequences of conditional branches that all test a single integer value,
|
||||
/// and simplify them into a ILAst switch instruction (which like C# does not require contiguous values).
|
||||
/// </summary>
|
||||
class SwitchDetection : IILTransform |
||||
{ |
||||
SwitchAnalysis analysis = new SwitchAnalysis(); |
||||
|
||||
public void Run(ILFunction function, ILTransformContext context) |
||||
{ |
||||
foreach (var container in function.Descendants.OfType<BlockContainer>()) { |
||||
bool needBlockCleanup = false; |
||||
foreach (var block in container.Blocks) { |
||||
if (analysis.AnalyzeBlock(block) && UseCSharpSwitch(analysis)) { |
||||
var hugeSection = analysis.Sections.Single(s => s.Key.Count() > 50); |
||||
|
||||
var sw = new SwitchInstruction(new LdLoc(analysis.SwitchVariable)); |
||||
foreach (var section in analysis.Sections) { |
||||
if (!section.Key.SetEquals(hugeSection.Key)) { |
||||
sw.Sections.Add(new SwitchSection |
||||
{ |
||||
Labels = section.Key, |
||||
Body = section.Value |
||||
}); |
||||
} |
||||
} |
||||
block.Instructions[block.Instructions.Count - 2] = sw; |
||||
block.Instructions[block.Instructions.Count - 1] = hugeSection.Value; |
||||
// mark all inner blocks that were converted to the switch statement for deletion
|
||||
foreach (var innerBlock in analysis.InnerBlocks) { |
||||
Debug.Assert(innerBlock.Parent == container); |
||||
Debug.Assert(innerBlock != container.EntryPoint); |
||||
innerBlock.Instructions.Clear(); |
||||
} |
||||
needBlockCleanup = true; |
||||
} |
||||
} |
||||
if (needBlockCleanup) { |
||||
Debug.Assert(container.Blocks.All(b => b.Instructions.Count != 0 || b.IncomingEdgeCount == 0)); |
||||
container.Blocks.RemoveAll(b => b.Instructions.Count == 0); |
||||
} |
||||
} |
||||
} |
||||
|
||||
const ulong MaxValuesPerSection = 50; |
||||
|
||||
/// <summary>
|
||||
/// Tests whether we should prefer a switch statement over an if statement.
|
||||
/// </summary>
|
||||
static bool UseCSharpSwitch(SwitchAnalysis analysis) |
||||
{ |
||||
return analysis.InnerBlocks.Any() |
||||
&& analysis.Sections.Count(s => s.Key.Count() > MaxValuesPerSection) == 1; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,301 @@
@@ -0,0 +1,301 @@
|
||||
// Copyright (c) 2016 Daniel Grunwald
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.Immutable; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
|
||||
namespace ICSharpCode.Decompiler |
||||
{ |
||||
/// <summary>
|
||||
/// An immutable set of longs, that is implemented as a list of intervals.
|
||||
/// </summary>
|
||||
public struct LongSet : IEquatable<LongSet> |
||||
{ |
||||
public readonly ImmutableArray<LongInterval> Intervals; |
||||
|
||||
public LongSet(ImmutableArray<LongInterval> intervals) |
||||
{ |
||||
this.Intervals = intervals; |
||||
#if DEBUG
|
||||
long minValue = long.MinValue; |
||||
for (int i = 0; i < intervals.Length; i++) { |
||||
Debug.Assert(!intervals[i].IsEmpty); |
||||
Debug.Assert(minValue <= intervals[i].Start); |
||||
if (intervals[i].InclusiveEnd == long.MaxValue - 1 || intervals[i].InclusiveEnd == long.MaxValue) { |
||||
// An inclusive end of long.MaxValue-1 or long.MaxValue means (after the gap of 1 element),
|
||||
// there isn't any room for more non-empty intervals.
|
||||
Debug.Assert(i == intervals.Length - 1); |
||||
} else { |
||||
minValue = checked(intervals[i].End + 1); // enforce least 1 gap between intervals
|
||||
} |
||||
} |
||||
#endif
|
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Create a new LongSet that contains a single value.
|
||||
/// </summary>
|
||||
public LongSet(long value) |
||||
: this(ImmutableArray.Create(LongInterval.Inclusive(value, value))) |
||||
{ |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Create a new LongSet that contains the values from the interval.
|
||||
/// </summary>
|
||||
public LongSet(LongInterval interval) |
||||
: this(interval.IsEmpty ? Empty.Intervals : ImmutableArray.Create(interval)) |
||||
{ |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// The empty LongSet.
|
||||
/// </summary>
|
||||
public static readonly LongSet Empty = new LongSet(ImmutableArray.Create<LongInterval>()); |
||||
|
||||
/// <summary>
|
||||
/// The LongSet that contains all possible long values.
|
||||
/// </summary>
|
||||
public static readonly LongSet Universe = new LongSet(LongInterval.Inclusive(long.MinValue, long.MaxValue)); |
||||
|
||||
public bool IsEmpty |
||||
{ |
||||
get { return Intervals.IsEmpty; } |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets the number of values in this LongSet.
|
||||
/// Note: for <c>LongSet.Universe</c>, the number of values does not fit into <c>ulong</c>.
|
||||
/// Instead, this property returns the off-by-one value <c>ulong.MaxValue</c> to avoid overflow.
|
||||
/// </summary>
|
||||
public ulong Count() |
||||
{ |
||||
unchecked { |
||||
ulong count = 0; |
||||
foreach (var interval in Intervals) { |
||||
count += (ulong)(interval.End - interval.Start); |
||||
} |
||||
if (count == 0 && !Intervals.IsEmpty) |
||||
return ulong.MaxValue; |
||||
else |
||||
return count; |
||||
} |
||||
} |
||||
|
||||
|
||||
IEnumerable<LongInterval> DoIntersectWith(LongSet other) |
||||
{ |
||||
var enumA = this.Intervals.GetEnumerator(); |
||||
var enumB = other.Intervals.GetEnumerator(); |
||||
bool moreA = enumA.MoveNext(); |
||||
bool moreB = enumB.MoveNext(); |
||||
while (moreA && moreB) { |
||||
LongInterval a = enumA.Current; |
||||
LongInterval b = enumB.Current; |
||||
LongInterval intersection = a.Intersect(b); |
||||
if (!intersection.IsEmpty) { |
||||
yield return intersection; |
||||
} |
||||
if (a.InclusiveEnd < b.InclusiveEnd) { |
||||
moreA = enumA.MoveNext(); |
||||
} else { |
||||
moreB = enumB.MoveNext(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public bool Overlaps(LongSet other) |
||||
{ |
||||
return DoIntersectWith(other).Any(); |
||||
} |
||||
|
||||
public LongSet IntersectWith(LongSet other) |
||||
{ |
||||
return new LongSet(DoIntersectWith(other).ToImmutableArray()); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Given an enumerable of non-empty intervals sorted by the starting position,
|
||||
/// merges overlapping or touching intervals to create a valid interval array for LongSet.
|
||||
/// </summary>
|
||||
static IEnumerable<LongInterval> MergeOverlapping(IEnumerable<LongInterval> input) |
||||
{ |
||||
long start = long.MinValue; |
||||
long end = long.MinValue; |
||||
bool empty = true; |
||||
foreach (var element in input) { |
||||
Debug.Assert(start <= element.Start); |
||||
Debug.Assert(!element.IsEmpty); |
||||
|
||||
if (!empty && element.Start <= end) { |
||||
// element overlaps or touches [start, end), so combine the intervals:
|
||||
if (element.End == long.MinValue) { |
||||
// special case: element goes all the way up to long.MaxValue inclusive
|
||||
end = long.MinValue; |
||||
} else { |
||||
end = Math.Max(end, element.End); |
||||
} |
||||
} else { |
||||
// flush existing interval:
|
||||
if (!empty) { |
||||
yield return new LongInterval(start, end); |
||||
} else { |
||||
empty = false; |
||||
} |
||||
start = element.Start; |
||||
end = element.End; |
||||
} |
||||
if (end == long.MinValue) { |
||||
// special case: element goes all the way up to long.MaxValue inclusive
|
||||
// all further intervals in the input must be contained in [start, end),
|
||||
// so ignore them (and avoid trouble due to the overflow in `end`).
|
||||
break; |
||||
} |
||||
} |
||||
if (!empty) { |
||||
yield return new LongInterval(start, end); |
||||
} |
||||
} |
||||
|
||||
public LongSet UnionWith(LongSet other) |
||||
{ |
||||
var mergedIntervals = this.Intervals.Merge(other.Intervals, (a, b) => a.Start.CompareTo(b.Start)); |
||||
return new LongSet(MergeOverlapping(mergedIntervals).ToImmutableArray()); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Creates a new LongSet where val is added to each element of this LongSet.
|
||||
/// </summary>
|
||||
public LongSet AddOffset(long val) |
||||
{ |
||||
var newIntervals = new List<LongInterval>(Intervals.Length + 1); |
||||
foreach (var element in Intervals) { |
||||
long newStart = unchecked(element.Start + val); |
||||
long newInclusiveEnd = unchecked(element.InclusiveEnd + val); |
||||
if (newStart <= newInclusiveEnd) { |
||||
newIntervals.Add(LongInterval.Inclusive(newStart, newInclusiveEnd)); |
||||
} else { |
||||
// interval got split by integer overflow
|
||||
newIntervals.Add(LongInterval.Inclusive(newStart, long.MaxValue)); |
||||
newIntervals.Add(LongInterval.Inclusive(long.MinValue, newInclusiveEnd)); |
||||
} |
||||
} |
||||
newIntervals.Sort((a, b) => a.Start.CompareTo(b.Start)); |
||||
return new LongSet(MergeOverlapping(newIntervals).ToImmutableArray()); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Creates a new set that contains all values that are in <c>this</c>, but not in <c>other</c>.
|
||||
/// </summary>
|
||||
public LongSet ExceptWith(LongSet other) |
||||
{ |
||||
return IntersectWith(other.Invert()); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Creates a new LongSet that contains all elements not contained in this LongSet.
|
||||
/// </summary>
|
||||
public LongSet Invert() |
||||
{ |
||||
// The loop below assumes a non-empty LongSet, so handle the empty case specially.
|
||||
if (IsEmpty) { |
||||
return Universe; |
||||
} |
||||
List<LongInterval> newIntervals = new List<LongInterval>(Intervals.Length + 1); |
||||
long prevEnd = long.MinValue; // previous exclusive end
|
||||
foreach (var interval in Intervals) { |
||||
if (interval.Start > prevEnd) { |
||||
newIntervals.Add(new LongInterval(prevEnd, interval.Start)); |
||||
} |
||||
prevEnd = interval.End; |
||||
} |
||||
// create a final interval up to long.MaxValue inclusive
|
||||
if (prevEnd != long.MinValue) { |
||||
newIntervals.Add(new LongInterval(prevEnd, long.MinValue)); |
||||
} |
||||
return new LongSet(newIntervals.ToImmutableArray()); |
||||
} |
||||
|
||||
public bool Contains(long val) |
||||
{ |
||||
int index = upper_bound(val); |
||||
return index > 0 && Intervals[index - 1].Contains(val); |
||||
} |
||||
|
||||
internal int upper_bound(long val) |
||||
{ |
||||
int min = 0, max = Intervals.Length - 1; |
||||
while (max >= min) { |
||||
int m = min + (max - min) / 2; |
||||
LongInterval i = Intervals[m]; |
||||
if (val < i.Start) { |
||||
max = m - 1; |
||||
continue; |
||||
} |
||||
if (val > i.End) { |
||||
min = m + 1; |
||||
continue; |
||||
} |
||||
return m + 1; |
||||
} |
||||
return min; |
||||
} |
||||
|
||||
public IEnumerable<long> Values |
||||
{ |
||||
get { return Intervals.SelectMany(i => i.Range()); } |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
return string.Join(",", Intervals); |
||||
} |
||||
|
||||
#region Equals and GetHashCode implementation
|
||||
public override bool Equals(object obj) |
||||
{ |
||||
return obj is LongSet && SetEquals((LongSet)obj); |
||||
} |
||||
|
||||
public override int GetHashCode() |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
|
||||
[Obsolete("Explicitly call SetEquals() instead.")] |
||||
public bool Equals(LongSet other) |
||||
{ |
||||
return SetEquals(other); |
||||
} |
||||
|
||||
public bool SetEquals(LongSet other) |
||||
{ |
||||
if (Intervals.Length != other.Intervals.Length) |
||||
return false; |
||||
for (int i = 0; i < Intervals.Length; i++) { |
||||
if (Intervals[i] != other.Intervals[i]) |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
#endregion
|
||||
} |
||||
} |
Loading…
Reference in new issue