internal static extern unsafe int CompareString(SafeSortHandle sortHandle, char* lpStr1, int cwStr1Len, char* lpStr2, int cwStr2Len, CompareOptions options);
[DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOf")]
- internal static extern unsafe int IndexOf(SafeSortHandle sortHandle, string target, int cwTargetLength, char* pSource, int cwSourceLength, CompareOptions options, int* matchLengthPtr);
- [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOf")]
internal static extern unsafe int IndexOf(SafeSortHandle sortHandle, char* target, int cwTargetLength, char* pSource, int cwSourceLength, CompareOptions options, int* matchLengthPtr);
[DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_LastIndexOf")]
- internal static extern unsafe int LastIndexOf(SafeSortHandle sortHandle, string target, int cwTargetLength, char* pSource, int cwSourceLength, CompareOptions options);
+ internal static extern unsafe int LastIndexOf(SafeSortHandle sortHandle, char* target, int cwTargetLength, char* pSource, int cwSourceLength, CompareOptions options);
[DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOfOrdinalIgnoreCase")]
internal static extern unsafe int IndexOfOrdinalIgnoreCase(string target, int cwTargetLength, char* pSource, int cwSourceLength, bool findLast);
fixed (char* pSource = source) fixed (char* pValue = value)
{
char* pSrc = &pSource[startIndex];
- int index = InvariantFindString(pSrc, count, pValue, value.Length, ignoreCase, start : true);
+ int index = InvariantFindString(pSrc, count, pValue, value.Length, ignoreCase, fromBeginning : true);
if (index >= 0)
{
return index + startIndex;
}
}
- internal static unsafe int InvariantIndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
+ internal static unsafe int InvariantIndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase, bool fromBeginning = true)
{
Debug.Assert(source.Length != 0);
Debug.Assert(value.Length != 0);
fixed (char* pSource = &MemoryMarshal.GetReference(source))
fixed (char* pValue = &MemoryMarshal.GetReference(value))
{
- return InvariantFindString(pSource, source.Length, pValue, value.Length, ignoreCase, start: true);
+ return InvariantFindString(pSource, source.Length, pValue, value.Length, ignoreCase, fromBeginning);
}
}
fixed (char* pSource = source) fixed (char* pValue = value)
{
char* pSrc = &pSource[startIndex - count + 1];
- int index = InvariantFindString(pSrc, count, pValue, value.Length, ignoreCase, start : false);
+ int index = InvariantFindString(pSrc, count, pValue, value.Length, ignoreCase, fromBeginning : false);
if (index >= 0)
{
return index + startIndex - count + 1;
}
}
- private static unsafe int InvariantFindString(char* source, int sourceCount, char* value, int valueCount, bool ignoreCase, bool start)
+ private static unsafe int InvariantFindString(char* source, int sourceCount, char* value, int valueCount, bool ignoreCase, bool fromBeginning)
{
int ctrSource = 0; // index value into source
int ctrValue = 0; // index value into value
if (valueCount == 0)
{
- return start ? 0 : sourceCount - 1;
+ return fromBeginning ? 0 : sourceCount - 1;
}
if (sourceCount < valueCount)
return -1;
}
- if (start)
+ if (fromBeginning)
{
lastSourceStart = sourceCount - valueCount;
if (ignoreCase)
return -1;
}
- internal static unsafe int IndexOfOrdinalCore(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
+ internal static unsafe int IndexOfOrdinalCore(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase, bool fromBeginning)
{
Debug.Assert(!GlobalizationMode.Invariant);
fixed (char* pSource = &MemoryMarshal.GetReference(source))
fixed (char* pValue = &MemoryMarshal.GetReference(value))
{
- int index = Interop.Globalization.IndexOfOrdinalIgnoreCase(pValue, value.Length, pSource, source.Length, findLast: false);
- return index;
+ return Interop.Globalization.IndexOfOrdinalIgnoreCase(pValue, value.Length, pSource, source.Length, findLast: !fromBeginning);
}
}
- int endIndex = source.Length - value.Length;
- for (int i = 0; i <= endIndex; i++)
+ int startIndex, endIndex, jump;
+ if (fromBeginning)
+ {
+ // Left to right, from zero to last possible index in the source string.
+ // Incrementing by one after each iteration. Stop condition is last possible index plus 1.
+ startIndex = 0;
+ endIndex = source.Length - value.Length + 1;
+ jump = 1;
+ }
+ else
+ {
+ // Right to left, from first possible index in the source string to zero.
+ // Decrementing by one after each iteration. Stop condition is last possible index minus 1.
+ startIndex = source.Length - value.Length;
+ endIndex = -1;
+ jump = -1;
+ }
+
+ for (int i = startIndex; i != endIndex; i += jump)
{
int valueIndex, sourceIndex;
#endif
fixed (char* pSource = source)
+ fixed (char* pTarget = target)
{
- index = Interop.Globalization.IndexOf(_sortHandle, target, target.Length, pSource + startIndex, count, options, matchLengthPtr);
+ index = Interop.Globalization.IndexOf(_sortHandle, pTarget, target.Length, pSource + startIndex, count, options, matchLengthPtr);
return index != -1 ? index + startIndex : -1;
}
}
// For now, this method is only called from Span APIs with either options == CompareOptions.None or CompareOptions.IgnoreCase
- internal unsafe int IndexOfCore(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr)
+ internal unsafe int IndexOfCore(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr, bool fromBeginning)
{
Debug.Assert(!_invariantMode);
Debug.Assert(source.Length != 0);
if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options))
{
if ((options & CompareOptions.IgnoreCase) == CompareOptions.IgnoreCase)
- {
- return IndexOfOrdinalIgnoreCaseHelper(source, target, options, matchLengthPtr);
- }
+ return IndexOfOrdinalIgnoreCaseHelper(source, target, options, matchLengthPtr, fromBeginning);
else
- {
- return IndexOfOrdinalHelper(source, target, options, matchLengthPtr);
- }
+ return IndexOfOrdinalHelper(source, target, options, matchLengthPtr, fromBeginning);
}
else
{
fixed (char* pSource = &MemoryMarshal.GetReference(source))
fixed (char* pTarget = &MemoryMarshal.GetReference(target))
{
- return Interop.Globalization.IndexOf(_sortHandle, pTarget, target.Length, pSource, source.Length, options, matchLengthPtr);
+ if (fromBeginning)
+ return Interop.Globalization.IndexOf(_sortHandle, pTarget, target.Length, pSource, source.Length, options, matchLengthPtr);
+ else
+ return Interop.Globalization.LastIndexOf(_sortHandle, pTarget, target.Length, pSource, source.Length, options);
}
}
}
- private unsafe int IndexOfOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr)
+ /// <summary>
+ /// Duplicate of IndexOfOrdinalHelper that also handles ignore case. Can't converge both methods
+ /// as the JIT wouldn't be able to optimize the ignoreCase path away.
+ /// </summary>
+ /// <returns></returns>
+ private unsafe int IndexOfOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr, bool fromBeginning)
{
Debug.Assert(!_invariantMode);
{
char* a = ap;
char* b = bp;
- int endIndex = source.Length - target.Length;
- if (endIndex < 0)
+ if (target.Length > source.Length)
goto InteropCall;
for (int j = 0; j < target.Length; j++)
goto InteropCall;
}
- int i = 0;
- for (; i <= endIndex; i++)
+ int startIndex, endIndex, jump;
+ if (fromBeginning)
+ {
+ // Left to right, from zero to last possible index in the source string.
+ // Incrementing by one after each iteration. Stop condition is last possible index plus 1.
+ startIndex = 0;
+ endIndex = source.Length - target.Length + 1;
+ jump = 1;
+ }
+ else
+ {
+ // Right to left, from first possible index in the source string to zero.
+ // Decrementing by one after each iteration. Stop condition is last possible index minus 1.
+ startIndex = source.Length - target.Length;
+ endIndex = -1;
+ jump = -1;
+ }
+
+ for (int i = startIndex; i != endIndex; i += jump)
{
int targetIndex = 0;
int sourceIndex = i;
- for (; targetIndex < target.Length; targetIndex++)
+ for (; targetIndex < target.Length; targetIndex++, sourceIndex++)
{
char valueChar = *(a + sourceIndex);
char targetChar = *(b + targetIndex);
if (valueChar == targetChar && valueChar < 0x80 && !s_highCharTable[valueChar])
{
- sourceIndex++;
continue;
}
goto InteropCall;
else if (valueChar != targetChar)
break;
- sourceIndex++;
}
if (targetIndex == target.Length)
return i;
}
}
- if (i > endIndex)
- return -1;
- InteropCall:
- return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr);
+
+ return -1;
+ InteropCall:
+ if (fromBeginning)
+ return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr);
+ else
+ return Interop.Globalization.LastIndexOf(_sortHandle, b, target.Length, a, source.Length, options);
}
}
- private unsafe int IndexOfOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr)
+ private unsafe int IndexOfOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr, bool fromBeginning)
{
Debug.Assert(!_invariantMode);
{
char* a = ap;
char* b = bp;
- int endIndex = source.Length - target.Length;
- if (endIndex < 0)
+ if (target.Length > source.Length)
goto InteropCall;
for (int j = 0; j < target.Length; j++)
goto InteropCall;
}
- int i = 0;
- for (; i <= endIndex; i++)
+ int startIndex, endIndex, jump;
+ if (fromBeginning)
+ {
+ // Left to right, from zero to last possible index in the source string.
+ // Incrementing by one after each iteration. Stop condition is last possible index plus 1.
+ startIndex = 0;
+ endIndex = source.Length - target.Length + 1;
+ jump = 1;
+ }
+ else
+ {
+ // Right to left, from first possible index in the source string to zero.
+ // Decrementing by one after each iteration. Stop condition is last possible index minus 1.
+ startIndex = source.Length - target.Length;
+ endIndex = -1;
+ jump = -1;
+ }
+
+ for (int i = startIndex; i != endIndex; i += jump)
{
int targetIndex = 0;
int sourceIndex = i;
- for (; targetIndex < target.Length; targetIndex++)
+ for (; targetIndex < target.Length; targetIndex++, sourceIndex++)
{
char valueChar = *(a + sourceIndex);
char targetChar = *(b + targetIndex);
+
if (valueChar >= 0x80 || s_highCharTable[valueChar])
goto InteropCall;
else if (valueChar != targetChar)
break;
- sourceIndex++;
}
if (targetIndex == target.Length)
return i;
}
}
- if (i > endIndex)
- return -1;
+
+ return -1;
InteropCall:
- return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr);
+ if (fromBeginning)
+ return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr);
+ else
+ return Interop.Globalization.LastIndexOf(_sortHandle, b, target.Length, a, source.Length, options);
}
}
int leftStartIndex = (startIndex - count + 1);
fixed (char* pSource = source)
+ fixed (char* pTarget = target)
{
- int lastIndex = Interop.Globalization.LastIndexOf(_sortHandle, target, target.Length, pSource + (startIndex - count + 1), count, options);
+ int lastIndex = Interop.Globalization.LastIndexOf(_sortHandle, pTarget, target.Length, pSource + (startIndex - count + 1), count, options);
return lastIndex != -1 ? lastIndex + leftStartIndex : -1;
}
return FindStringOrdinal(FIND_FROMSTART, source, startIndex, count, value, value.Length, ignoreCase);
}
- internal static int IndexOfOrdinalCore(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
+ internal static int IndexOfOrdinalCore(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase, bool fromBeginning)
{
Debug.Assert(!GlobalizationMode.Invariant);
Debug.Assert(source.Length != 0);
Debug.Assert(value.Length != 0);
- return FindStringOrdinal(FIND_FROMSTART, source, value, ignoreCase);
+ uint positionFlag = fromBeginning ? (uint)FIND_FROMSTART : FIND_FROMEND;
+ return FindStringOrdinal(positionFlag, source, value, ignoreCase);
}
internal static int LastIndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase)
return -1;
}
- internal unsafe int IndexOfCore(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr)
+ internal unsafe int IndexOfCore(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr, bool fromBeginning)
{
Debug.Assert(!_invariantMode);
Debug.Assert(target.Length != 0);
Debug.Assert((options == CompareOptions.None || options == CompareOptions.IgnoreCase));
- int retValue = FindString(FIND_FROMSTART | (uint)GetNativeCompareFlags(options), source, target, matchLengthPtr);
- return retValue;
+ uint positionFlag = fromBeginning ? (uint)FIND_FROMSTART : FIND_FROMEND;
+ return FindString(positionFlag | (uint)GetNativeCompareFlags(options), source, target, matchLengthPtr);
}
private unsafe int LastIndexOfCore(string source, string target, int startIndex, int count, CompareOptions options)
Debug.Assert(!_invariantMode);
Debug.Assert(!source.IsEmpty);
Debug.Assert(!value.IsEmpty);
- return IndexOfOrdinalCore(source, value, ignoreCase);
+ return IndexOfOrdinalCore(source, value, ignoreCase, fromBeginning: true);
+ }
+
+ internal int LastIndexOfOrdinal(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
+ {
+ Debug.Assert(!_invariantMode);
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!value.IsEmpty);
+ return IndexOfOrdinalCore(source, value, ignoreCase, fromBeginning: false);
}
internal unsafe int IndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, CompareOptions options)
Debug.Assert(!_invariantMode);
Debug.Assert(!source.IsEmpty);
Debug.Assert(!value.IsEmpty);
- return IndexOfCore(source, value, options, null);
+ return IndexOfCore(source, value, options, null, fromBeginning: true);
+ }
+
+ internal unsafe int LastIndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!value.IsEmpty);
+ return IndexOfCore(source, value, options, null, fromBeginning: false);
}
// The following IndexOf overload is mainly used by String.Replace. This overload assumes the parameters are already validated
case StringComparison.InvariantCultureIgnoreCase:
return CompareInfo.Invariant.IndexOf(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType));
- case StringComparison.Ordinal:
- case StringComparison.OrdinalIgnoreCase:
+ default:
+ Debug.Assert(comparisonType == StringComparison.Ordinal || comparisonType == StringComparison.OrdinalIgnoreCase);
return CompareInfo.Invariant.IndexOfOrdinal(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType) != CompareOptions.None);
}
+ }
- Debug.Fail("StringComparison outside range");
- return -1;
+ /// <summary>
+ /// Reports the zero-based index of the last occurrence of the specified <paramref name="value"/> in the current <paramref name="span"/>.
+ /// <param name="span">The source span.</param>
+ /// <param name="value">The value to seek within the source span.</param>
+ /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="value"/> are compared.</param>
+ /// </summary>
+ public static int LastIndexOf(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
+ {
+ string.CheckStringComparison(comparisonType);
+
+ if (value.Length == 0)
+ {
+ return 0;
+ }
+
+ if (span.Length == 0)
+ {
+ return -1;
+ }
+
+ if (GlobalizationMode.Invariant)
+ {
+ return CompareInfo.InvariantIndexOf(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType) != CompareOptions.None, fromBeginning: false);
+ }
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ case StringComparison.CurrentCultureIgnoreCase:
+ return CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType));
+
+ case StringComparison.InvariantCulture:
+ case StringComparison.InvariantCultureIgnoreCase:
+ return CompareInfo.Invariant.LastIndexOf(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType));
+
+ default:
+ Debug.Assert(comparisonType == StringComparison.Ordinal || comparisonType == StringComparison.OrdinalIgnoreCase);
+ return CompareInfo.Invariant.LastIndexOfOrdinal(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType) != CompareOptions.None);
+ }
}
/// <summary>