Skip to content

Commit ec8806f

Browse files
Add index and range polyfills
1 parent 17e0d3f commit ec8806f

File tree

2 files changed

+272
-0
lines changed

2 files changed

+272
-0
lines changed

src/PowerShellEditorServices/Index.cs

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// <auto-generated>
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
5+
using System.Diagnostics.CodeAnalysis;
6+
using System.Runtime.CompilerServices;
7+
8+
namespace System
9+
{
10+
/// <summary>Represent a type can be used to index a collection either from the start or the end.</summary>
11+
/// <remarks>
12+
/// Index is used by the C# compiler to support the new index syntax
13+
/// <code>
14+
/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ;
15+
/// int lastElement = someArray[^1]; // lastElement = 5
16+
/// </code>
17+
/// </remarks>
18+
internal readonly struct Index : IEquatable<Index>
19+
{
20+
private readonly int _value;
21+
22+
/// <summary>Construct an Index using a value and indicating if the index is from the start or from the end.</summary>
23+
/// <param name="value">The index value. it has to be zero or positive number.</param>
24+
/// <param name="fromEnd">Indicating if the index is from the start or from the end.</param>
25+
/// <remarks>
26+
/// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element.
27+
/// </remarks>
28+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
29+
public Index(int value, bool fromEnd = false)
30+
{
31+
if (value < 0)
32+
{
33+
ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
34+
}
35+
36+
if (fromEnd)
37+
_value = ~value;
38+
else
39+
_value = value;
40+
}
41+
42+
// The following private constructors mainly created for perf reason to avoid the checks
43+
private Index(int value) => _value = value;
44+
45+
/// <summary>Create an Index pointing at first element.</summary>
46+
public static Index Start => new Index(0);
47+
48+
/// <summary>Create an Index pointing at beyond last element.</summary>
49+
public static Index End => new Index(~0);
50+
51+
/// <summary>Create an Index from the start at the position indicated by the value.</summary>
52+
/// <param name="value">The index value from the start.</param>
53+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
54+
public static Index FromStart(int value)
55+
{
56+
if (value < 0)
57+
{
58+
ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
59+
}
60+
61+
return new Index(value);
62+
}
63+
64+
/// <summary>Create an Index from the end at the position indicated by the value.</summary>
65+
/// <param name="value">The index value from the end.</param>
66+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
67+
public static Index FromEnd(int value)
68+
{
69+
if (value < 0)
70+
{
71+
ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
72+
}
73+
74+
return new Index(~value);
75+
}
76+
77+
/// <summary>Returns the index value.</summary>
78+
public int Value
79+
{
80+
get
81+
{
82+
if (_value < 0)
83+
return ~_value;
84+
else
85+
return _value;
86+
}
87+
}
88+
89+
/// <summary>Indicates whether the index is from the start or the end.</summary>
90+
public bool IsFromEnd => _value < 0;
91+
92+
/// <summary>Calculate the offset from the start using the giving collection length.</summary>
93+
/// <param name="length">The length of the collection that the Index will be used with. length has to be a positive value</param>
94+
/// <remarks>
95+
/// For performance reason, we don't validate the input length parameter and the returned offset value against negative values.
96+
/// we don't validate either the returned offset is greater than the input length.
97+
/// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and
98+
/// then used to index a collection will get out of range exception which will be same affect as the validation.
99+
/// </remarks>
100+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
101+
public int GetOffset(int length)
102+
{
103+
int offset = _value;
104+
if (IsFromEnd)
105+
{
106+
// offset = length - (~value)
107+
// offset = length + (~(~value) + 1)
108+
// offset = length + value + 1
109+
110+
offset += length + 1;
111+
}
112+
return offset;
113+
}
114+
115+
/// <summary>Indicates whether the current Index object is equal to another object of the same type.</summary>
116+
/// <param name="value">An object to compare with this object</param>
117+
public override bool Equals([NotNullWhen(true)] object? value) => value is Index && _value == ((Index)value)._value;
118+
119+
/// <summary>Indicates whether the current Index object is equal to another Index object.</summary>
120+
/// <param name="other">An object to compare with this object</param>
121+
public bool Equals(Index other) => _value == other._value;
122+
123+
/// <summary>Returns the hash code for this instance.</summary>
124+
public override int GetHashCode() => _value;
125+
126+
/// <summary>Converts integer number to an Index.</summary>
127+
public static implicit operator Index(int value) => FromStart(value);
128+
129+
/// <summary>Converts the value of the current Index object to its equivalent string representation.</summary>
130+
public override string ToString()
131+
{
132+
if (IsFromEnd)
133+
return ToStringFromEnd();
134+
135+
return ((uint)Value).ToString();
136+
}
137+
138+
private string ToStringFromEnd()
139+
{
140+
return '^' + Value.ToString();
141+
}
142+
143+
internal static class ThrowHelper
144+
{
145+
[DoesNotReturn, MethodImpl(MethodImplOptions.NoInlining)]
146+
public static void ThrowValueArgumentOutOfRange_NeedNonNegNumException()
147+
{
148+
throw new ArgumentOutOfRangeException(
149+
"Non-negative number required. (Parameter 'value')",
150+
"value");
151+
}
152+
}
153+
}
154+
}

src/PowerShellEditorServices/Range.cs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// <auto-generated>
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
5+
global using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range;
6+
7+
#nullable enable
8+
9+
using System.Diagnostics.CodeAnalysis;
10+
using System.Runtime.CompilerServices;
11+
12+
namespace System
13+
{
14+
/// <summary>Represent a range has start and end indexes.</summary>
15+
/// <remarks>
16+
/// Range is used by the C# compiler to support the range syntax.
17+
/// <code>
18+
/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 };
19+
/// int[] subArray1 = someArray[0..2]; // { 1, 2 }
20+
/// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 }
21+
/// </code>
22+
/// </remarks>
23+
internal readonly struct Range : IEquatable<Range>
24+
{
25+
/// <summary>Represent the inclusive start index of the Range.</summary>
26+
public Index Start { get; }
27+
28+
/// <summary>Represent the exclusive end index of the Range.</summary>
29+
public Index End { get; }
30+
31+
/// <summary>Construct a Range object using the start and end indexes.</summary>
32+
/// <param name="start">Represent the inclusive start index of the range.</param>
33+
/// <param name="end">Represent the exclusive end index of the range.</param>
34+
public Range(Index start, Index end)
35+
{
36+
Start = start;
37+
End = end;
38+
}
39+
40+
/// <summary>Indicates whether the current Range object is equal to another object of the same type.</summary>
41+
/// <param name="value">An object to compare with this object</param>
42+
public override bool Equals([NotNullWhen(true)] object? value) =>
43+
value is Range r &&
44+
r.Start.Equals(Start) &&
45+
r.End.Equals(End);
46+
47+
/// <summary>Indicates whether the current Range object is equal to another Range object.</summary>
48+
/// <param name="other">An object to compare with this object</param>
49+
public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End);
50+
51+
/// <summary>Returns the hash code for this instance.</summary>
52+
public override int GetHashCode()
53+
{
54+
return (Start.GetHashCode(), End.GetHashCode()).GetHashCode();
55+
}
56+
57+
/// <summary>Converts the value of the current Range object to its equivalent string representation.</summary>
58+
public override string ToString()
59+
{
60+
return Start.ToString() + ".." + End.ToString();
61+
}
62+
63+
/// <summary>Create a Range object starting from start index to the end of the collection.</summary>
64+
public static Range StartAt(Index start) => new Range(start, Index.End);
65+
66+
/// <summary>Create a Range object starting from first element in the collection to the end Index.</summary>
67+
public static Range EndAt(Index end) => new Range(Index.Start, end);
68+
69+
/// <summary>Create a Range object starting from first element to the end.</summary>
70+
public static Range All => new Range(Index.Start, Index.End);
71+
72+
/// <summary>Calculate the start offset and length of range object using a collection length.</summary>
73+
/// <param name="length">The length of the collection that the range will be used with. length has to be a positive value.</param>
74+
/// <remarks>
75+
/// For performance reason, we don't validate the input length parameter against negative values.
76+
/// It is expected Range will be used with collections which always have non negative length/count.
77+
/// We validate the range is inside the length scope though.
78+
/// </remarks>
79+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
80+
public (int Offset, int Length) GetOffsetAndLength(int length)
81+
{
82+
int start;
83+
Index startIndex = Start;
84+
if (startIndex.IsFromEnd)
85+
start = length - startIndex.Value;
86+
else
87+
start = startIndex.Value;
88+
89+
int end;
90+
Index endIndex = End;
91+
if (endIndex.IsFromEnd)
92+
end = length - endIndex.Value;
93+
else
94+
end = endIndex.Value;
95+
96+
if ((uint)end > (uint)length || (uint)start > (uint)end)
97+
{
98+
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length);
99+
}
100+
101+
return (start, end - start);
102+
}
103+
104+
private static class ExceptionArgument
105+
{
106+
public const string length = nameof(length);
107+
}
108+
109+
private static class ThrowHelper
110+
{
111+
[DoesNotReturn, MethodImpl(MethodImplOptions.NoInlining)]
112+
public static void ThrowArgumentOutOfRangeException(string parameterName)
113+
{
114+
throw new ArgumentOutOfRangeException(parameterName);
115+
}
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)