Skip to content

Conversation

@binarycow
Copy link
Contributor

This PR adds to TextSpan, an indexer, and support for the range operator (..)

Exactly how well this is supported depends on the target framework of the application that uses this library:

  • .NET Standard 2.1, .NET Core 3, or later: The feature just works (as demonstrated in the unit tests)
  • .NET Standard 2.0, or .NET Framework, with a polyfill package such as IndexRange - again, it just works
  • .NET Standard 2.0, or .NET Framework, without a polyfill package: The range operator isn't available, but the indexer and Slice methods work as expected.

@nblumhardt
Copy link
Member

Thanks for the PR! Are there any places in the Superpower codebase, or typical usage patterns, where slicing is useful with TextSpan? (Just a standard consideration when adding any feature, to keep the library nice and compact.) Thanks!

@binarycow
Copy link
Contributor Author

binarycow commented Sep 2, 2024

Are there any places in the Superpower codebase, or typical usage patterns, where slicing is useful with TextSpan?

Well...

  • Instead of textSpan.Skip(10), you could use textSpan[10..]
  • Instead of textSpan.First(textSpan.Length - 10), you could use textSpan[..^10]
  • Instead of textSpan.First(10), you could use textSpan.Slice(0, 10). You can't use the range operator here, because the second argument to the range operator is the index from the end. That makes this particular usage not so great, as you can just call First, and not have to have the 0 operator.

Primarily, it should be supported because other similar types* support slicing, such as ArraySegment<T>, ReadOnlySpan<T>, Span<T>, Memory<T>, ReadOnlyMemory<T>, ReadOnlySequence<T>. Even ImmutableArray<T> supports slicing.

One benefit to meeting the slicing "interface" (it's duck-typed, not a true interface), aside from using the range operator (..) is providing an API that is familiar to developers who are not used to dealing with TextSpan. They don't need to figure out that they want First and Skip. They don't need to read the xmldocs to see what the parameters mean. It's Slice, just like every other slicable type.


My specific use case:

I "reached for" the non-existant slice capability in a parser I was writing. This language has three different kinds of strings (unquoted, single-quoted, double-quoted). Those strings can be appended. Double quoted strings support escaping and trimming (the trimming is much like C#'s raw strings).

I found it easiest to, in my parsing method, just accept the token values - quotes and all - and then pass the final result to another method that would handle concatenation, escaping, trimming, etc. That method would use the quotes to know how to handle that portion of the string. It would then strip the quotes off, with a simple span[1..^1]. Instead, I have to do this: span.First(span.Length - 1).Skip(1).


* Notably, StringSegment in Microsoft.Extensions.Primitives (the most direct comparison to TextSpan) does not support slicing, but this is probably because it's fairly new, is trying to match the string API (so it uses Substring instead of Slice), and it is not special-cased by the compiler, like string is (for string, the compiler will use Substring instead of Slice, and for arrays, the compiler will use System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray instead of Slice). I have a feeling that eventually, especially if StringSegment catches on as a non-ref-struct counterpart to ReadOnlySpan<char>, they'll either special case StringSegment, or support SubString in addition to Slice.

Copy link
Member

@nblumhardt nblumhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the follow-up. I realise now that you haven't made the changes you mention because these would require a polyfill to keep the netstandard2.0 target happy; I think the choice to skip internal changes is the right one 👍

LGTM, just had one trivial nitpick

return true;
}


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit - extra newline

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realise now that you haven't made the changes you mention because these would require a polyfill to keep the netstandard2.0 target happy

Yeah. As it is, if they're using .NET Standard 2.0, then they'd need to use the polyfill to use the range operator (..).

If they're using .NET Core (not sure what the minimum version is), it just works, without polyfill package.

@nblumhardt nblumhardt merged commit 0d3af97 into datalust:dev Sep 4, 2024
@nblumhardt
Copy link
Member

Thanks @binarycow 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants