diff --git a/Solutions/Corvus.HighPerformance.Specs/Corvus.HighPerformance.Specs.csproj b/Solutions/Corvus.HighPerformance.Specs/Corvus.HighPerformance.Specs.csproj
index 136ce1f..53a550b 100644
--- a/Solutions/Corvus.HighPerformance.Specs/Corvus.HighPerformance.Specs.csproj
+++ b/Solutions/Corvus.HighPerformance.Specs/Corvus.HighPerformance.Specs.csproj
@@ -55,8 +55,4 @@
-
-
-
-
diff --git a/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/AppendUtf8.feature b/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/AppendUtf8.feature
new file mode 100644
index 0000000..633cf2c
--- /dev/null
+++ b/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/AppendUtf8.feature
@@ -0,0 +1,61 @@
+Feature: Append content into a UTF-8 ValueStringBuilder
+
+Scenario Outline: Fits in available space
+ Given a UTF-8 ValueStringBuilder initialized with '' of length
+ And I append '' to the UTF-8 ValueStringBuilder
+ And I append '' to the UTF-8 ValueStringBuilder
+ When I get the string from the UTF-8 ValueStringBuilder
+ Then the UTF-8 ValueStringBuilder string should be ''
+ Examples:
+ | InitializationType | InitialLength | FirstValue | SecondValue |
+ | Span | 11 | Hello | World! |
+ | Capacity | 11 | Hello | World! |
+ | Span | 20 | Hello | World! |
+ | Capacity | 20 | Hello | World! |
+
+Scenario Outline: Grows
+ Given a UTF-8 ValueStringBuilder initialized with '' of length
+ And I append '' to the UTF-8 ValueStringBuilder
+ And I append '' to the UTF-8 ValueStringBuilder
+ When I get the string from the UTF-8 ValueStringBuilder via ''
+ Then the UTF-8 ValueStringBuilder string should be ''
+ Examples:
+ | InitializationType | InitialLength | FirstValue | SecondValue | GetStringMechanism |
+ | Span | 11 | Hello, world! | It is mighty fine to see you today. | CreateStringAndDispose |
+ | Capacity | 11 | Hello, world! | It is mighty fine to see you today. | CreateStringAndDispose |
+ | Span | 11 | Hello, world! | It is mighty fine to see you today. | RentedBuffer |
+ | Capacity | 11 | Hello, world! | It is mighty fine to see you today. | RentedBuffer |
+
+Scenario Outline: Append number
+ Given a UTF-8 ValueStringBuilder initialized with '' of length
+ And I append '' to the UTF-8 ValueStringBuilder
+ And I append the Int32 to the UTF-8 ValueStringBuilder
+ When I get the string from the UTF-8 ValueStringBuilder
+ Then the UTF-8 ValueStringBuilder string should be ''
+ Examples:
+ | InitializationType | InitialLength | FirstValue | SecondValue |
+ | Span | 13 | Hello, world! | 42 |
+ | Capacity | 13 | Hello, world! | 42 |
+ | Span | 15 | Hello, world! | 42 |
+ | Capacity | 15 | Hello, world! | 42 |
+ | Span | 13 | Hello, world! | -42 |
+ | Capacity | 13 | Hello, world! | -42 |
+ | Span | 15 | Hello, world! | -42 |
+ | Capacity | 15 | Hello, world! | -42 |
+
+Scenario Outline: Append number then string
+ Given a UTF-8 ValueStringBuilder initialized with '' of length
+ And I append the Int32 to the UTF-8 ValueStringBuilder
+ And I append '' to the UTF-8 ValueStringBuilder
+ When I get the string from the UTF-8 ValueStringBuilder
+ Then the UTF-8 ValueStringBuilder string should be ''
+ Examples:
+ | InitializationType | InitialLength | FirstValue | SecondValue |
+ | Span | 13 | 42 | Hello, world! |
+ | Capacity | 13 | 42 | Hello, world! |
+ | Span | 15 | 42 | Hello, world! |
+ | Capacity | 15 | 42 | Hello, world! |
+ | Span | 13 | -42 | Hello, world! |
+ | Capacity | 13 | -42 | Hello, world! |
+ | Span | 15 | -42 | Hello, world! |
+ | Capacity | 15 | -42 | Hello, world! |
diff --git a/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/AsMemoryUtf8.feature b/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/AsMemoryUtf8.feature
new file mode 100644
index 0000000..a19e560
--- /dev/null
+++ b/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/AsMemoryUtf8.feature
@@ -0,0 +1,67 @@
+Feature: Retrieve a Memory from a UTF-8 ValueStringBuilder
+
+Scenario Outline: Retrieve a Memory from a UTF-8 ValueStringBuilder
+ Given a UTF-8 ValueStringBuilder initialized with '' of length
+ And I append 'Hello' to the UTF-8 ValueStringBuilder
+ And I append 'World!' to the UTF-8 ValueStringBuilder
+ When I get the Memory from the UTF-8 ValueStringBuilder
+ Then the UTF-8 ValueStringBuilder string should be 'HelloWorld!'
+ Examples:
+ | InitializationType | InitialLength |
+ | Span | 10 |
+ | Capacity | 10 |
+ | Span | 11 |
+ | Capacity | 11 |
+ | Span | 20 |
+ | Capacity | 20 |
+
+Scenario Outline: Retrieve a Memory with a start position from a UTF-8 ValueStringBuilder
+ Given a UTF-8 ValueStringBuilder initialized with '' of length
+ And I append 'Hello' to the UTF-8 ValueStringBuilder
+ And I append 'World!' to the UTF-8 ValueStringBuilder
+ When I get the Memory starting at from the UTF-8 ValueStringBuilder
+ Then the UTF-8 ValueStringBuilder string should be ''
+ Examples:
+ | InitializationType | InitialLength | Start | ExpectedValue |
+ | Span | 10 | 0 | HelloWorld! |
+ | Capacity | 10 | 0 | HelloWorld! |
+ | Span | 20 | 0 | HelloWorld! |
+ | Capacity | 20 | 0 | HelloWorld! |
+ | Span | 10 | 3 | loWorld! |
+ | Capacity | 10 | 3 | loWorld! |
+ | Span | 20 | 3 | loWorld! |
+ | Capacity | 20 | 3 | loWorld! |
+
+
+Scenario Outline: Retrieve a Memory with a start position and length from a UTF-8 ValueStringBuilder
+ Given a UTF-8 ValueStringBuilder initialized with '' of length
+ And I append 'Hello' to the UTF-8 ValueStringBuilder
+ And I append 'World!' to the UTF-8 ValueStringBuilder
+ When I get the Memory starting at with length from the UTF-8 ValueStringBuilder
+ Then the UTF-8 ValueStringBuilder string should be ''
+ Examples:
+ | InitializationType | InitialLength | Start | Length | ExpectedValue |
+ | Span | 10 | 0 | 11 | HelloWorld! |
+ | Capacity | 10 | 0 | 11 | HelloWorld! |
+ | Span | 20 | 0 | 11 | HelloWorld! |
+ | Capacity | 20 | 0 | 11 | HelloWorld! |
+ | Span | 10 | 0 | 0 | |
+ | Capacity | 10 | 0 | 0 | |
+ | Span | 20 | 0 | 0 | |
+ | Capacity | 20 | 0 | 0 | |
+ | Span | 10 | 5 | 0 | |
+ | Capacity | 10 | 5 | 0 | |
+ | Span | 20 | 5 | 0 | |
+ | Capacity | 20 | 5 | 0 | |
+ | Span | 10 | 3 | 8 | loWorld! |
+ | Capacity | 10 | 3 | 8 | loWorld! |
+ | Span | 20 | 3 | 8 | loWorld! |
+ | Capacity | 20 | 3 | 8 | loWorld! |
+ | Span | 10 | 3 | 3 | loW |
+ | Capacity | 10 | 3 | 3 | loW |
+ | Span | 20 | 3 | 3 | loW |
+ | Capacity | 20 | 3 | 3 | loW |
+ | Span | 10 | 10 | 1 | ! |
+ | Capacity | 10 | 10 | 1 | ! |
+ | Span | 20 | 10 | 1 | ! |
+ | Capacity | 20 | 10 | 1 | ! |
diff --git a/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/AsSpanUtf8.feature b/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/AsSpanUtf8.feature
new file mode 100644
index 0000000..34e76bc
--- /dev/null
+++ b/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/AsSpanUtf8.feature
@@ -0,0 +1,67 @@
+Feature: Retrieve a Span from a UTF-8 ValueStringBuilder
+
+Scenario Outline: Retrieve a Span from a UTF-8 ValueStringBuilder
+ Given a UTF-8 ValueStringBuilder initialized with '' of length
+ And I append 'Hello' to the UTF-8 ValueStringBuilder
+ And I append 'World!' to the UTF-8 ValueStringBuilder
+ When I get the Span from the UTF-8 ValueStringBuilder
+ Then the UTF-8 ValueStringBuilder string should be 'HelloWorld!'
+ Examples:
+ | InitializationType | InitialLength |
+ | Span | 10 |
+ | Capacity | 10 |
+ | Span | 11 |
+ | Capacity | 11 |
+ | Span | 20 |
+ | Capacity | 20 |
+
+Scenario Outline: Retrieve a Span with a start position from a UTF-8 ValueStringBuilder
+ Given a UTF-8 ValueStringBuilder initialized with '' of length
+ And I append 'Hello' to the UTF-8 ValueStringBuilder
+ And I append 'World!' to the UTF-8 ValueStringBuilder
+ When I get the Span starting at from the UTF-8 ValueStringBuilder
+ Then the UTF-8 ValueStringBuilder string should be ''
+ Examples:
+ | InitializationType | InitialLength | Start | ExpectedValue |
+ | Span | 10 | 0 | HelloWorld! |
+ | Capacity | 10 | 0 | HelloWorld! |
+ | Span | 20 | 0 | HelloWorld! |
+ | Capacity | 20 | 0 | HelloWorld! |
+ | Span | 10 | 3 | loWorld! |
+ | Capacity | 10 | 3 | loWorld! |
+ | Span | 20 | 3 | loWorld! |
+ | Capacity | 20 | 3 | loWorld! |
+
+
+Scenario Outline: Retrieve a Span with a start position and length from a UTF-8 ValueStringBuilder
+ Given a UTF-8 ValueStringBuilder initialized with '' of length
+ And I append 'Hello' to the UTF-8 ValueStringBuilder
+ And I append 'World!' to the UTF-8 ValueStringBuilder
+ When I get the Span starting at with length from the UTF-8 ValueStringBuilder
+ Then the UTF-8 ValueStringBuilder string should be ''
+ Examples:
+ | InitializationType | InitialLength | Start | Length | ExpectedValue |
+ | Span | 10 | 0 | 11 | HelloWorld! |
+ | Capacity | 10 | 0 | 11 | HelloWorld! |
+ | Span | 20 | 0 | 11 | HelloWorld! |
+ | Capacity | 20 | 0 | 11 | HelloWorld! |
+ | Span | 10 | 0 | 0 | |
+ | Capacity | 10 | 0 | 0 | |
+ | Span | 20 | 0 | 0 | |
+ | Capacity | 20 | 0 | 0 | |
+ | Span | 10 | 5 | 0 | |
+ | Capacity | 10 | 5 | 0 | |
+ | Span | 20 | 5 | 0 | |
+ | Capacity | 20 | 5 | 0 | |
+ | Span | 10 | 3 | 8 | loWorld! |
+ | Capacity | 10 | 3 | 8 | loWorld! |
+ | Span | 20 | 3 | 8 | loWorld! |
+ | Capacity | 20 | 3 | 8 | loWorld! |
+ | Span | 10 | 3 | 3 | loW |
+ | Capacity | 10 | 3 | 3 | loW |
+ | Span | 20 | 3 | 3 | loW |
+ | Capacity | 20 | 3 | 3 | loW |
+ | Span | 10 | 10 | 1 | ! |
+ | Capacity | 10 | 10 | 1 | ! |
+ | Span | 20 | 10 | 1 | ! |
+ | Capacity | 20 | 10 | 1 | ! |
diff --git a/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/ReplaceUtf8.feature b/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/ReplaceUtf8.feature
new file mode 100644
index 0000000..42e92b2
--- /dev/null
+++ b/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/ReplaceUtf8.feature
@@ -0,0 +1,197 @@
+Feature: Replace content in a UTF-8 ValueStringBuilder
+
+Scenario Outline: No change
+ Given a UTF-8 ValueStringBuilder initialized with '' of length 13
+ And I append 'Hello, World!' to the UTF-8 ValueStringBuilder
+ And I replace 'notpresent' with 'notused' at index 0 with count 13 in the UTF-8 ValueStringBuilder
+ When I get the string from the UTF-8 ValueStringBuilder
+ Then the UTF-8 ValueStringBuilder string should be 'Hello, World!'
+ Examples:
+ | InitializationType |
+ | Span |
+ | Capacity |
+
+Scenario Outline: Shrinks
+ Given a UTF-8 ValueStringBuilder initialized with '' of length 23
+ And I append 'Hello, World or wherever!' to the UTF-8 ValueStringBuilder
+ And I replace '' with '' at index with count in the UTF-8 ValueStringBuilder
+ When I get the string from the UTF-8 ValueStringBuilder
+ Then the UTF-8 ValueStringBuilder string should be ''
+ Examples:
+ | InitializationType | Find | Replacement | Start | Length | Result |
+ | Span | ell | i | 0 | 13 | Hio, World or wherever! |
+ | Capacity | ell | i | 0 | 13 | Hio, World or wherever! |
+ | Span | ell | i | 1 | 12 | Hio, World or wherever! |
+ | Capacity | ell | i | 1 | 12 | Hio, World or wherever! |
+ | Span | ell | i | 1 | 3 | Hio, World or wherever! |
+ | Capacity | ell | i | 1 | 3 | Hio, World or wherever! |
+ | Span | ell | i | 1 | 2 | Hello, World or wherever! |
+ | Capacity | ell | i | 1 | 2 | Hello, World or wherever! |
+ | Span | ell | i | 2 | 11 | Hello, World or wherever! |
+ | Capacity | ell | i | 2 | 11 | Hello, World or wherever! |
+ | Span | or | x | 2 | 23 | Hello, Wxld x wherever! |
+ | Capacity | or | x | 2 | 23 | Hello, Wxld x wherever! |
+ | Span | or | x | 2 | 10 | Hello, Wxld or wherever! |
+ | Capacity | or | x | 2 | 10 | Hello, Wxld or wherever! |
+ | Span | or | x | 9 | 6 | Hello, World x wherever! |
+ | Capacity | or | x | 9 | 6 | Hello, World x wherever! |
+
+Scenario Outline: Changes without length change
+ Given a UTF-8 ValueStringBuilder initialized with '' of length 23
+ And I append 'Hello, World or wherever!' to the UTF-8 ValueStringBuilder
+ And I replace '' with '' at index with count in the UTF-8 ValueStringBuilder
+ When I get the string from the UTF-8 ValueStringBuilder
+ Then the UTF-8 ValueStringBuilder string should be ''
+ Examples:
+ | InitializationType | Find | Replacement | Start | Length | Result |
+ | Span | ell | ijk | 0 | 13 | Hijko, World or wherever! |
+ | Capacity | ell | ijk | 0 | 13 | Hijko, World or wherever! |
+ | Span | ell | ijk | 1 | 12 | Hijko, World or wherever! |
+ | Capacity | ell | ijk | 1 | 12 | Hijko, World or wherever! |
+ | Span | ell | ijk | 1 | 3 | Hijko, World or wherever! |
+ | Capacity | ell | ijk | 1 | 3 | Hijko, World or wherever! |
+ | Span | ell | ijk | 1 | 2 | Hello, World or wherever! |
+ | Capacity | ell | ijk | 1 | 2 | Hello, World or wherever! |
+ | Span | ell | ijk | 2 | 11 | Hello, World or wherever! |
+ | Capacity | ell | ijk | 2 | 11 | Hello, World or wherever! |
+ | Span | or | al | 2 | 23 | Hello, Walld al wherever! |
+ | Capacity | or | al | 2 | 23 | Hello, Walld al wherever! |
+ | Span | or | al | 2 | 10 | Hello, Walld or wherever! |
+ | Capacity | or | al | 2 | 10 | Hello, Walld or wherever! |
+ | Span | or | al | 9 | 6 | Hello, World al wherever! |
+ | Capacity | or | al | 9 | 6 | Hello, World al wherever! |
+
+Scenario Outline: Grows but fits in available space
+ Given a UTF-8 ValueStringBuilder initialized with '' of length 25
+ And I append 'Hello, World or wherever!' to the UTF-8 ValueStringBuilder
+ And I replace '' with '' at index with count in the UTF-8 ValueStringBuilder
+ When I get the string from the UTF-8 ValueStringBuilder
+ Then the UTF-8 ValueStringBuilder string should be ''
+ Examples:
+ | InitializationType | Find | Replacement | Start | Length | Result |
+ | Span | ell | ijkx | 0 | 13 | Hijkxo, World or wherever! |
+ | Capacity | ell | ijkx | 0 | 13 | Hijkxo, World or wherever! |
+ | Span | ell | ijkx | 1 | 12 | Hijkxo, World or wherever! |
+ | Capacity | ell | ijkx | 1 | 12 | Hijkxo, World or wherever! |
+ | Span | ell | ijkx | 1 | 3 | Hijkxo, World or wherever! |
+ | Capacity | ell | ijkx | 1 | 3 | Hijkxo, World or wherever! |
+ | Span | ell | ijkx | 1 | 2 | Hello, World or wherever! |
+ | Capacity | ell | ijkx | 1 | 2 | Hello, World or wherever! |
+ | Span | ell | ijkx | 2 | 11 | Hello, World or wherever! |
+ | Capacity | ell | ijkx | 2 | 11 | Hello, World or wherever! |
+ | Span | or | alz | 2 | 23 | Hello, Walzld alz wherever! |
+ | Capacity | or | alz | 2 | 23 | Hello, Walzld alz wherever! |
+ | Span | or | alz | 2 | 10 | Hello, Walzld or wherever! |
+ | Capacity | or | alz | 2 | 10 | Hello, Walzld or wherever! |
+ | Span | or | alz | 9 | 6 | Hello, World alz wherever! |
+ | Capacity | or | alz | 9 | 6 | Hello, World alz wherever! |
+ | Span | ell | ijkxwv | 0 | 13 | Hijkxwvo, World or wherever! |
+ | Capacity | ell | ijkxwv | 0 | 13 | Hijkxwvo, World or wherever! |
+ | Span | ell | ijkxwv | 1 | 12 | Hijkxwvo, World or wherever! |
+ | Capacity | ell | ijkxwv | 1 | 12 | Hijkxwvo, World or wherever! |
+ | Span | ell | ijkxwv | 1 | 3 | Hijkxwvo, World or wherever! |
+ | Capacity | ell | ijkxwv | 1 | 3 | Hijkxwvo, World or wherever! |
+ | Span | ell | ijkxwv | 1 | 2 | Hello, World or wherever! |
+ | Capacity | ell | ijkxwv | 1 | 2 | Hello, World or wherever! |
+ | Span | ell | ijkxwv | 2 | 11 | Hello, World or wherever! |
+ | Capacity | ell | ijkxwv | 2 | 11 | Hello, World or wherever! |
+ | Span | or | alzyh | 2 | 23 | Hello, Walzyhld alzyh wherever! |
+ | Capacity | or | alzyh | 2 | 23 | Hello, Walzyhld alzyh wherever! |
+ | Span | or | alzyh | 2 | 10 | Hello, Walzyhld or wherever! |
+ | Capacity | or | alzyh | 2 | 10 | Hello, Walzyhld or wherever! |
+ | Span | or | alzyh | 9 | 6 | Hello, World alzyh wherever! |
+ | Capacity | or | alzyh | 9 | 6 | Hello, World alzyh wherever! |
+
+
+Scenario Outline: Grows and requires resize
+ Given a UTF-8 ValueStringBuilder initialized with '' of length 32
+ And I append 'Hello, World or wherever!padpadp' to the UTF-8 ValueStringBuilder
+ And I replace '' with '' at index with count in the UTF-8 ValueStringBuilder
+ When I get the string from the UTF-8 ValueStringBuilder
+ Then the UTF-8 ValueStringBuilder string should be ''
+ Examples:
+ | InitializationType | Find | Replacement | Start | Length | Result |
+ | Span | ell | ijkx | 0 | 13 | Hijkxo, World or wherever!padpadp |
+ | Capacity | ell | ijkx | 0 | 13 | Hijkxo, World or wherever!padpadp |
+ | Span | ell | ijkx | 1 | 12 | Hijkxo, World or wherever!padpadp |
+ | Capacity | ell | ijkx | 1 | 12 | Hijkxo, World or wherever!padpadp |
+ | Span | ell | ijkx | 1 | 3 | Hijkxo, World or wherever!padpadp |
+ | Capacity | ell | ijkx | 1 | 3 | Hijkxo, World or wherever!padpadp |
+ | Span | ell | ijkx | 1 | 2 | Hello, World or wherever!padpadp |
+ | Capacity | ell | ijkx | 1 | 2 | Hello, World or wherever!padpadp |
+ | Span | ell | ijkx | 2 | 11 | Hello, World or wherever!padpadp |
+ | Capacity | ell | ijkx | 2 | 11 | Hello, World or wherever!padpadp |
+ | Span | or | alz | 2 | 23 | Hello, Walzld alz wherever!padpadp |
+ | Capacity | or | alz | 2 | 23 | Hello, Walzld alz wherever!padpadp |
+ | Span | or | alz | 2 | 10 | Hello, Walzld or wherever!padpadp |
+ | Capacity | or | alz | 2 | 10 | Hello, Walzld or wherever!padpadp |
+ | Span | or | alz | 9 | 6 | Hello, World alz wherever!padpadp |
+ | Capacity | or | alz | 9 | 6 | Hello, World alz wherever!padpadp |
+ | Span | ell | ijkxwv | 0 | 13 | Hijkxwvo, World or wherever!padpadp |
+ | Capacity | ell | ijkxwv | 0 | 13 | Hijkxwvo, World or wherever!padpadp |
+ | Span | ell | ijkxwv | 1 | 12 | Hijkxwvo, World or wherever!padpadp |
+ | Capacity | ell | ijkxwv | 1 | 12 | Hijkxwvo, World or wherever!padpadp |
+ | Span | ell | ijkxwv | 1 | 3 | Hijkxwvo, World or wherever!padpadp |
+ | Capacity | ell | ijkxwv | 1 | 3 | Hijkxwvo, World or wherever!padpadp |
+ | Span | ell | ijkxwv | 1 | 2 | Hello, World or wherever!padpadp |
+ | Capacity | ell | ijkxwv | 1 | 2 | Hello, World or wherever!padpadp |
+ | Span | ell | ijkxwv | 2 | 11 | Hello, World or wherever!padpadp |
+ | Capacity | ell | ijkxwv | 2 | 11 | Hello, World or wherever!padpadp |
+ | Span | or | alzyh | 2 | 23 | Hello, Walzyhld alzyh wherever!padpadp |
+ | Capacity | or | alzyh | 2 | 23 | Hello, Walzyhld alzyh wherever!padpadp |
+ | Span | or | alzyh | 2 | 10 | Hello, Walzyhld or wherever!padpadp |
+ | Capacity | or | alzyh | 2 | 10 | Hello, Walzyhld or wherever!padpadp |
+ | Span | or | alzyh | 9 | 6 | Hello, World alzyh wherever!padpadp |
+ | Capacity | or | alzyh | 9 | 6 | Hello, World alzyh wherever!padpadp |
+
+
+Scenario Outline: Text not present
+ Given a UTF-8 ValueStringBuilder initialized with '' of length 25
+ And I append 'Hello, World or wherever!' to the UTF-8 ValueStringBuilder
+ And I replace '' with '' at index with count in the UTF-8 ValueStringBuilder
+ When I get the string from the UTF-8 ValueStringBuilder
+ Then the UTF-8 ValueStringBuilder string should be 'Hello, World or wherever!'
+ Examples:
+ | InitializationType | Find | Replacement | Start | Length |
+ | Span | foo | ijkx | 0 | 25 |
+ | Capacity | foo | ijkx | 0 | 25 |
+ | Span | elloo | ijkx | 0 | 25 |
+ | Capacity | elloo | ijkx | 0 | 25 |
+
+
+Scenario Outline: Text not within specified range
+ Given a UTF-8 ValueStringBuilder initialized with '' of length 25
+ And I append 'Hello, World or wherever!' to the UTF-8 ValueStringBuilder
+ And I replace '' with '' at index with count in the UTF-8 ValueStringBuilder
+ When I get the string from the UTF-8 ValueStringBuilder
+ Then the UTF-8 ValueStringBuilder string should be 'Hello, World or wherever!'
+ Examples:
+ | InitializationType | Find | Replacement | Start | Length |
+ | Span | orld | ijkx | 0 | 8 |
+ | Capacity | orld | ijkx | 0 | 8 |
+ | Span | orld | ijkx | 0 | 8 |
+ | Capacity | orld | ijkx | 0 | 11 |
+ | Span | orld | ijkx | 8 | 3 |
+ | Capacity | orld | ijkx | 8 | 3 |
+ | Span | orld | ijkx | 9 | 16 |
+ | Capacity | orld | ijkx | 9 | 16 |
+ | Span | orld | ijkx | 12 | 13 |
+ | Capacity | orld | ijkx | 12 | 13 |
+ | Span | orld | ijkx | 0 | 0 |
+ | Capacity | orld | ijkx | 0 | 0 |
+ | Span | orld | ijkx | 8 | 0 |
+ | Capacity | orld | ijkx | 8 | 0 |
+
+Scenario Outline: Specified range is out of bounds
+ Given a UTF-8 ValueStringBuilder initialized with '' of length 25
+ And I append 'Hello, World or wherever!' to the UTF-8 ValueStringBuilder
+ And I attempt to replace '' with '' at index with count in the UTF-8 ValueStringBuilder
+ Then the attempt should have thrown a 'System.ArgumentOutOfRangeException'
+ Examples:
+ | InitializationType | Find | Replacement | Start | Length |
+ | Span | orld | ijkx | 0 | 26 |
+ | Capacity | orld | ijkx | 0 | 26 |
+ | Span | orld | ijkx | 1 | 25 |
+ | Capacity | orld | ijkx | 1 | 25 |
+ | Span | orld | ijkx | 10 | -4 |
+ | Capacity | orld | ijkx | 10 | -4 |
diff --git a/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/Utf8ValueStringBuilderStepDefinitions.cs b/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/Utf8ValueStringBuilderStepDefinitions.cs
new file mode 100644
index 0000000..ef91683
--- /dev/null
+++ b/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/Utf8ValueStringBuilderStepDefinitions.cs
@@ -0,0 +1,109 @@
+//
+// Copyright (c) Endjin Limited. All rights reserved.
+//
+
+using System.Text;
+
+using Corvus.HighPerformance.Specs;
+
+using Reqnroll;
+
+namespace ValueStringBuilderFeatures.Utf8;
+
+[Binding]
+public class Utf8ValueStringBuilderStepDefinitions(ExceptionStepDefinitions exceptionSteps)
+{
+ private Utf8ValueStringBuilderTestDriver? driver;
+
+ private Utf8ValueStringBuilderTestDriver Driver => this.driver ?? throw new InvalidOperationException("No step that initialized the driver has been executed");
+
+ [Given("a UTF-8 ValueStringBuilder initialized with {string} of length {int}")]
+ public void GivenAUtf8ValueStringBuilderInitializedWithOfLength(
+ Utf8ValueStringBuilderTestDriver.InitType intType, int initialCapacity)
+ {
+ this.driver = new(intType, initialCapacity);
+ }
+
+ [Given("I append {string} to the UTF-8 ValueStringBuilder")]
+ public void GivenIAppendToTheUtf8ValueStringBuilder(string text)
+ {
+ this.Driver.AddOperation(new Utf8ValueStringBuilderTestDriver.AppendOperation(Encoding.UTF8.GetBytes(text).AsMemory()));
+ }
+
+ [Given("I append the Int32 {int} to the UTF-8 ValueStringBuilder")]
+ public void GivenIAppendTheIntToTheUtf8ValueStringBuilder(int v)
+ {
+ this.Driver.AddOperation(new Utf8ValueStringBuilderTestDriver.AppendInt32Operation(v));
+ }
+
+ [Given("I replace {string} with {string} at index {int} with count {int} in the UTF-8 ValueStringBuilder")]
+ public void GivenIReplaceWithAtIndexWithCount(
+ string oldValue, string newValue, int startIndex, int count)
+ {
+ this.Driver.AddOperation(new Utf8ValueStringBuilderTestDriver.ReplaceOperation(
+ Encoding.UTF8.GetBytes(oldValue).AsMemory(), Encoding.UTF8.GetBytes(newValue).AsMemory(), startIndex, count));
+ }
+
+ [Given("I attempt to replace {string} with {string} at index {int} with count {int} in the UTF-8 ValueStringBuilder")]
+ public void GivenIAttemptToReplaceWithAtIndexWithCount(
+ string oldValue, string newValue, int startIndex, int count)
+ {
+ this.Driver.AddOperation(new Utf8ValueStringBuilderTestDriver.AttemptReplaceOperation(
+ Encoding.UTF8.GetBytes(oldValue).AsMemory(), Encoding.UTF8.GetBytes(newValue).AsMemory(), startIndex, count, exceptionSteps));
+ this.Driver.Execute();
+ }
+
+ [When("I get the string from the UTF-8 ValueStringBuilder via {string}")]
+ public void WhenIGetTheStringFromTheUtf8ValueStringBuilderVia(Utf8ValueStringBuilderValueFrom mechanism)
+ {
+ this.Driver.Execute(valueFrom: mechanism);
+ }
+
+ [When("I get the string from the UTF-8 ValueStringBuilder")]
+ public void WhenIGetTheStringFromTheUtf8ValueStringBuilder()
+ {
+ this.WhenIGetTheStringFromTheUtf8ValueStringBuilderVia(Utf8ValueStringBuilderValueFrom.CreateStringAndDispose);
+ }
+
+ [When("I get the Memory starting at {int} with length {int} from the UTF-8 ValueStringBuilder")]
+ public void WhenIGetTheMemoryStartingAtWithLengthFromTheUtf8ValueStringBuilder(int start, int length)
+ {
+ this.Driver.Execute(Utf8ValueStringBuilderValueFrom.Memory, start, length);
+ }
+
+ [When("I get the Memory starting at {int} from the UTF-8 ValueStringBuilder")]
+ public void WhenIGetTheMemoryStartingAtFromTheUtf8ValueStringBuilder(int start)
+ {
+ this.Driver.Execute(Utf8ValueStringBuilderValueFrom.Memory, start);
+ }
+
+ [When("I get the Memory from the UTF-8 ValueStringBuilder")]
+ public void WhenIGetTheMemoryFromTheUtf8ValueStringBuilder()
+ {
+ this.Driver.Execute(Utf8ValueStringBuilderValueFrom.Memory);
+ }
+
+ [When("I get the Span starting at {int} with length {int} from the UTF-8 ValueStringBuilder")]
+ public void WhenIGetTheSpanStartingAtWithLengthFromTheUtf8ValueStringBuilder(int start, int length)
+ {
+ this.Driver.Execute(Utf8ValueStringBuilderValueFrom.Span, start, length);
+ }
+
+ [When("I get the Span starting at {int} from the UTF-8 ValueStringBuilder")]
+ public void WhenIGetTheSpanStartingAtFromTheUtf8ValueStringBuilder(int start)
+ {
+ this.Driver.Execute(Utf8ValueStringBuilderValueFrom.Span, start);
+ }
+
+ [When("I get the Span from the UTF-8 ValueStringBuilder")]
+ public void WhenIGetTheSpanFromTheUtf8ValueStringBuilder()
+ {
+ this.Driver.Execute(Utf8ValueStringBuilderValueFrom.Span);
+ }
+
+ [Then("the UTF-8 ValueStringBuilder string should be {string}")]
+ public void ThenTheUtf8ValueStringBuilderStringShouldBe(string expectedResult)
+ {
+ Assert.IsTrue(Encoding.UTF8.GetBytes(expectedResult).AsMemory().Span.SequenceEqual(this.Driver.Result.Span));
+ }
+}
\ No newline at end of file
diff --git a/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/Utf8ValueStringBuilderTestDriver.cs b/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/Utf8ValueStringBuilderTestDriver.cs
new file mode 100644
index 0000000..25367f6
--- /dev/null
+++ b/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/Utf8ValueStringBuilderTestDriver.cs
@@ -0,0 +1,163 @@
+//
+// Copyright (c) Endjin Limited. All rights reserved.
+//
+
+using System.Globalization;
+using System.Text;
+
+using Corvus.HighPerformance.Specs;
+using Corvus.HighPerformance.Utf8;
+
+namespace ValueStringBuilderFeatures.Utf8;
+
+public class Utf8ValueStringBuilderTestDriver(
+ Utf8ValueStringBuilderTestDriver.InitType initType,
+ int initialCapacity)
+{
+ private readonly List operations = [];
+ private ReadOnlyMemory? result;
+
+ public enum InitType
+ {
+ Span,
+ Capacity,
+ }
+
+ public ReadOnlyMemory Result => this.result ?? throw new InvalidOperationException("Result requires Execute to have run");
+
+ public void AddOperation(OperationBase operation)
+ {
+ this.operations.Add(operation);
+ }
+
+ public void Execute(
+ Utf8ValueStringBuilderValueFrom valueFrom = Utf8ValueStringBuilderValueFrom.CreateStringAndDispose,
+ int? start = null,
+ int? length = null)
+ {
+ ValueStringBuilder sb = initType switch
+ {
+ InitType.Span => new ValueStringBuilder(stackalloc byte[initialCapacity]),
+ InitType.Capacity => new ValueStringBuilder(initialCapacity),
+ _ => throw new InvalidOperationException(),
+ };
+
+ foreach (OperationBase op in this.operations)
+ {
+ op.Execute(ref sb);
+ }
+
+ switch (valueFrom)
+ {
+ case Utf8ValueStringBuilderValueFrom.CreateStringAndDispose:
+ this.result = sb.CreateMemoryAndDispose();
+ break;
+
+ case Utf8ValueStringBuilderValueFrom.RentedBuffer:
+ (byte[]? rentedBuffer, int returnedLength) = sb.GetRentedBufferAndLengthAndDispose();
+ this.result = rentedBuffer.AsSpan(0, returnedLength).ToArray();
+ ValueStringBuilder.ReturnRentedBuffer(rentedBuffer);
+ break;
+
+ case Utf8ValueStringBuilderValueFrom.Memory:
+ if (length.HasValue)
+ {
+ if (!start.HasValue)
+ {
+ throw new ArgumentException("If length is specified, start must also be specified.", nameof(length));
+ }
+
+ this.result = sb.AsMemory(start.Value, length.Value);
+ }
+ else if (start.HasValue)
+ {
+ this.result = sb.AsMemory(start.Value);
+ }
+ else
+ {
+ this.result = sb.AsMemory();
+ }
+
+ break;
+
+ case Utf8ValueStringBuilderValueFrom.Span:
+ if (length.HasValue)
+ {
+ if (!start.HasValue)
+ {
+ throw new ArgumentException("If length is specified, start must also be specified.", nameof(length));
+ }
+
+ this.result = sb.AsSpan(start.Value, length.Value).ToArray();
+ }
+ else if (start.HasValue)
+ {
+ this.result = sb.AsSpan(start.Value).ToArray();
+ }
+ else
+ {
+ this.result = sb.AsSpan().ToArray();
+ }
+
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(valueFrom), valueFrom, null);
+ }
+ }
+
+ public abstract class OperationBase
+ {
+ public abstract void Execute(ref ValueStringBuilder sb);
+ }
+
+ public class AppendOperation(ReadOnlyMemory value) : OperationBase
+ {
+ public override void Execute(ref ValueStringBuilder sb)
+ {
+ sb.Append(value.Span);
+ }
+ }
+
+ public class AppendInt32Operation(int value) : OperationBase
+ {
+ public override void Execute(ref ValueStringBuilder sb)
+ {
+ sb.Append(value);
+ }
+ }
+
+ public class ReplaceOperation(
+ ReadOnlyMemory oldValue,
+ ReadOnlyMemory newValue,
+ int startIndex,
+ int count)
+ : OperationBase
+ {
+ public override void Execute(ref ValueStringBuilder sb)
+ {
+ sb.Replace(oldValue.Span, newValue.Span, startIndex, count);
+ }
+ }
+
+ public class AttemptReplaceOperation(
+ ReadOnlyMemory oldValue,
+ ReadOnlyMemory newValue,
+ int startIndex,
+ int count,
+ ExceptionStepDefinitions exceptionSteps)
+ : OperationBase
+ {
+ public override void Execute(ref ValueStringBuilder sb)
+ {
+ try
+ {
+ sb.Replace(oldValue.Span, newValue.Span, startIndex, count);
+ }
+ catch (Exception ex)
+ {
+ exceptionSteps.Exception = ex;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/Utf8ValueStringBuilderValueFrom.cs b/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/Utf8ValueStringBuilderValueFrom.cs
new file mode 100644
index 0000000..09e6d60
--- /dev/null
+++ b/Solutions/Corvus.HighPerformance.Specs/ValueStringBuilderFeatures/Utf8/Utf8ValueStringBuilderValueFrom.cs
@@ -0,0 +1,13 @@
+//
+// Copyright (c) Endjin Limited. All rights reserved.
+//
+
+namespace ValueStringBuilderFeatures.Utf8;
+
+public enum Utf8ValueStringBuilderValueFrom
+{
+ RentedBuffer,
+ CreateStringAndDispose,
+ Memory,
+ Span,
+}
\ No newline at end of file
diff --git a/Solutions/Corvus.HighPerformance.Specs/packages.lock.json b/Solutions/Corvus.HighPerformance.Specs/packages.lock.json
index dbca46b..a9d2df7 100644
--- a/Solutions/Corvus.HighPerformance.Specs/packages.lock.json
+++ b/Solutions/Corvus.HighPerformance.Specs/packages.lock.json
@@ -27,15 +27,6 @@
"Microsoft.CodeCoverage": "17.11.1"
}
},
- "Microsoft.NETFramework.ReferenceAssemblies": {
- "type": "Direct",
- "requested": "[1.0.3, )",
- "resolved": "1.0.3",
- "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
- "dependencies": {
- "Microsoft.NETFramework.ReferenceAssemblies.net481": "1.0.3"
- }
- },
"MSTest.TestAdapter": {
"type": "Direct",
"requested": "[3.6.0, )",
@@ -138,11 +129,6 @@
"System.Text.Json": "8.0.4"
}
},
- "Microsoft.NETFramework.ReferenceAssemblies.net481": {
- "type": "Transitive",
- "resolved": "1.0.3",
- "contentHash": "Vv/20vgHS7VglVOVh8J3Iz/MA+VYKVRp9f7r2qiKBMuzviTOmocG70yq0Q8T5OTmCONkEAIJwETD1zhEfLkAXQ=="
- },
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "1.1.1",
diff --git a/Solutions/Corvus.HighPerformance/Corvus.HighPerformance/Utf8/ValueStringBuilder.Replace.cs b/Solutions/Corvus.HighPerformance/Corvus.HighPerformance/Utf8/ValueStringBuilder.Replace.cs
new file mode 100644
index 0000000..f33089f
--- /dev/null
+++ b/Solutions/Corvus.HighPerformance/Corvus.HighPerformance/Utf8/ValueStringBuilder.Replace.cs
@@ -0,0 +1,155 @@
+//
+// Copyright (c) Endjin Limited. All rights reserved.
+//
+//
+// Derived from code Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See https://github.com/dotnet/runtime/blob/c1049390d5b33483203f058b0e1457d2a1f62bf4/src/libraries/Common/src/System/Text/ValueStringBuilder.cs
+//
+
+#pragma warning disable // Currently this is a straight copy of the original code, so we disable warnings to avoid diagnostic problems.
+
+#if !NET8_0_OR_GREATER
+using System.Runtime.CompilerServices;
+#endif
+
+namespace Corvus.HighPerformance.Utf8;
+
+public ref partial struct ValueStringBuilder
+{
+ public void Replace(
+ scoped ReadOnlySpan oldValue,
+ scoped ReadOnlySpan newValue,
+ int startIndex,
+ int count)
+ {
+ if (startIndex < 0 || (startIndex + count) > _pos)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex));
+ }
+
+ if (count == 0)
+ {
+ return;
+ }
+
+ Span rangeBuffer = _bytes.Slice(startIndex, count);
+
+ int diff = newValue.Length - oldValue.Length;
+ if (diff == 0)
+ {
+ int matchIndex = rangeBuffer.IndexOf(oldValue);
+ if (matchIndex == -1)
+ {
+ return;
+ }
+
+ Span remainingBuffer = rangeBuffer;
+ do
+ {
+ remainingBuffer = remainingBuffer[matchIndex..];
+ newValue.CopyTo(remainingBuffer);
+ remainingBuffer = remainingBuffer[oldValue.Length..];
+
+ matchIndex = remainingBuffer.IndexOf(oldValue);
+ } while (matchIndex != -1);
+
+ return;
+ }
+
+ if (diff < 0)
+ {
+ int matchIndex = rangeBuffer.IndexOf(oldValue);
+ if (matchIndex == -1)
+ {
+ return;
+ }
+
+ // We will never need to grow the buffer, but we might need to shift byteacters
+ // down.
+ Span remainingTargetBuffer = _bytes[(startIndex + matchIndex)..this._pos];
+ Span remainingSourceBuffer = remainingTargetBuffer;
+ int endOfSearchRangeRelativeToRemainingSourceBuffer = count - matchIndex;
+ do
+ {
+ this._pos += diff;
+
+ newValue.CopyTo(remainingTargetBuffer);
+
+ remainingSourceBuffer = remainingSourceBuffer[oldValue.Length..];
+ endOfSearchRangeRelativeToRemainingSourceBuffer -= oldValue.Length;
+ remainingTargetBuffer = remainingTargetBuffer[newValue.Length..];
+
+ matchIndex = remainingSourceBuffer[..endOfSearchRangeRelativeToRemainingSourceBuffer]
+ .IndexOf(oldValue);
+
+ int lengthOfChunkToRelocate = matchIndex == -1
+ ? remainingSourceBuffer.Length
+ : matchIndex;
+ remainingSourceBuffer[..lengthOfChunkToRelocate].CopyTo(remainingTargetBuffer);
+
+ remainingSourceBuffer = remainingSourceBuffer[lengthOfChunkToRelocate..];
+ endOfSearchRangeRelativeToRemainingSourceBuffer -= lengthOfChunkToRelocate;
+ remainingTargetBuffer = remainingTargetBuffer[lengthOfChunkToRelocate..];
+ } while (matchIndex != -1);
+
+ return;
+ }
+ else
+ {
+ int matchIndex = rangeBuffer.IndexOf(oldValue);
+ if (matchIndex == -1)
+ {
+ return;
+ }
+
+ Span matchIndexes = stackalloc int[(rangeBuffer.Length + oldValue.Length - 1) / oldValue.Length];
+
+ int matchCount = 0;
+ int currentRelocationDistance = 0;
+ while (matchIndex != -1)
+ {
+ matchIndexes[matchCount++] = matchIndex;
+ currentRelocationDistance += diff;
+
+ int nextIndex = rangeBuffer[(matchIndex + oldValue.Length)..].IndexOf(oldValue);
+ matchIndex = nextIndex == -1 ? -1 : matchIndex + nextIndex + oldValue.Length;
+ }
+
+ int relocationRangeEndIndex = this._pos;
+
+ int growBy = (this._pos + currentRelocationDistance) - _bytes.Length;
+ if (growBy > 0)
+ {
+ Grow(growBy);
+ }
+ this._pos += currentRelocationDistance;
+
+
+ // We work from the back of the string when growing to avoid having to
+ // shift anything more than once.
+ do
+ {
+ matchIndex = matchIndexes[matchCount - 1];
+
+ int relocationTargetStart = startIndex + matchIndex + oldValue.Length + currentRelocationDistance;
+ int relocationSourceStart = startIndex + matchIndex + oldValue.Length;
+ int endOfSearchRangeRelativeToRemainingSourceBuffer = count - matchIndex;
+
+ Span relocationTargetBuffer = _bytes[relocationTargetStart..];
+ Span sourceBuffer = _bytes[relocationSourceStart..relocationRangeEndIndex];
+
+ sourceBuffer.CopyTo(relocationTargetBuffer);
+
+ currentRelocationDistance -= diff;
+ Span replaceTargetBuffer = this._bytes.Slice(startIndex + matchIndex + currentRelocationDistance);
+ newValue.CopyTo(replaceTargetBuffer);
+
+ relocationRangeEndIndex = matchIndex + startIndex;
+ matchIndex = rangeBuffer[..matchIndex].LastIndexOf(oldValue);
+
+ matchCount -= 1;
+ } while (matchCount > 0);
+ }
+ }
+}
diff --git a/Solutions/Corvus.HighPerformance/Corvus.HighPerformance/Utf8/ValueStringBuilder.cs b/Solutions/Corvus.HighPerformance/Corvus.HighPerformance/Utf8/ValueStringBuilder.cs
new file mode 100644
index 0000000..516bbb6
--- /dev/null
+++ b/Solutions/Corvus.HighPerformance/Corvus.HighPerformance/Utf8/ValueStringBuilder.cs
@@ -0,0 +1,414 @@
+//
+// Copyright (c) Endjin Limited. All rights reserved.
+//
+//
+// Derived from code Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See https://github.com/dotnet/runtime/blob/c1049390d5b33483203f058b0e1457d2a1f62bf4/src/libraries/Common/src/System/Text/ValueStringBuilder.cs
+//
+
+#pragma warning disable // Currently this is a straight copy of the original code, so we disable warnings to avoid diagnostic problems.
+
+using System.Buffers;
+using System.Buffers.Text;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Diagnostics.Tracing;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+#nullable enable
+
+namespace Corvus.HighPerformance.Utf8;
+
+public ref partial struct ValueStringBuilder
+{
+ private byte[]? _arrayToReturnToPool;
+ private Span _bytes;
+ private int _pos;
+
+ public ValueStringBuilder(Span initialBuffer)
+ {
+ _arrayToReturnToPool = null;
+ _bytes = initialBuffer;
+ _pos = 0;
+ }
+
+ public ValueStringBuilder(int initialCapacity)
+ {
+ _arrayToReturnToPool = ArrayPool.Shared.Rent(initialCapacity);
+ _bytes = _arrayToReturnToPool;
+ _pos = 0;
+ }
+
+ public int Length
+ {
+ get
+ {
+ if (_pos < 0) { throw new ObjectDisposedException("Either Dispose or GetRentedBuffer or GetRentedBufferAndLength has already been called"); }
+ return _pos;
+ }
+
+ set
+ {
+ Debug.Assert(value >= 0);
+ Debug.Assert(value <= _bytes.Length);
+ _pos = value;
+ }
+ }
+
+ public int Capacity => _bytes.Length;
+
+ ///
+ /// Return the underlying rented buffer, if any, and the length of the string. This also
+ /// disposes the instance.
+ ///
+ ///
+ ///
+ /// Once you have retrieved this, you must not make any further use of
+ /// . You should call
+ /// once you no longer require the buffer.
+ ///
+ ///
+ public (byte[]? Buffer, int Length) GetRentedBufferAndLengthAndDispose()
+ {
+ byte[]? result = _arrayToReturnToPool;
+ int length = Length;
+ SetToDisposed();
+ return (result, length);
+ }
+
+ ///
+ /// Returns the buffer retrieved from .
+ ///
+ /// The buffer to return.
+ public static void ReturnRentedBuffer(byte[]? buffer)
+ {
+ ReturnRentedBuffer(buffer, false);
+ }
+
+ ///
+ /// Returns the buffer retrieved from .
+ ///
+ /// The buffer to return.
+ /// If then clear the buffer when returned.
+ public static void ReturnRentedBuffer(byte[]? buffer, bool clearBuffer)
+ {
+ if (buffer is byte[] b)
+ {
+ ArrayPool.Shared.Return(b, clearBuffer);
+ }
+ }
+
+ public void EnsureCapacity(int capacity)
+ {
+ // This is not expected to be called this with negative capacity
+ Debug.Assert(capacity >= 0);
+
+ // If the caller has a bug and calls this with negative capacity, make sure to call Grow to throw an exception.
+ if ((uint)capacity > (uint)_bytes.Length)
+ Grow(capacity - _pos);
+ }
+
+ ///
+ /// Get a pinnable reference to the builder.
+ /// Does not ensure there is a null byte after
+ /// This overload is pattern matched in the C# 7.3+ compiler so you can omit
+ /// the explicit method call, and write eg "fixed (byte* c = builder)"
+ ///
+ public ref byte GetPinnableReference()
+ {
+ return ref MemoryMarshal.GetReference(_bytes);
+ }
+
+ ///
+ /// Get a pinnable reference to the builder.
+ ///
+ /// Ensures that the builder has a null byte after
+ public ref byte GetPinnableReference(bool terminate)
+ {
+ if (terminate)
+ {
+ EnsureCapacity(Length + 1);
+ _bytes[Length] = (byte)'\0';
+ }
+ return ref MemoryMarshal.GetReference(_bytes);
+ }
+
+ public ref byte this[int index]
+ {
+ get
+ {
+ Debug.Assert(index < _pos);
+ return ref _bytes[index];
+ }
+ }
+
+ public ReadOnlyMemory CreateMemoryAndDispose()
+ {
+ ReadOnlyMemory s = _bytes.Slice(0, _pos).ToArray();
+ Dispose();
+ return s;
+ }
+
+ /// Returns the underlying storage of the builder.
+ public Span RawBytes => _bytes;
+
+ ///
+ /// Returns a span around the contents of the builder.
+ ///
+ /// Ensures that the builder has a null byte after
+ public ReadOnlySpan AsSpan(bool terminate)
+ {
+ if (terminate)
+ {
+ EnsureCapacity(Length + 1);
+ _bytes[Length] = (byte)'\0';
+ }
+ return _bytes.Slice(0, _pos);
+ }
+
+ public ReadOnlySpan AsSpan() => _bytes.Slice(0, _pos);
+ public ReadOnlySpan AsSpan(int start) => _bytes.Slice(start, _pos - start);
+ public ReadOnlySpan AsSpan(int start, int length) => _bytes.Slice(start, length);
+
+ ///
+ /// Returns the contents of the builder as a , renting the underlying buffer if it
+ /// is not already backed by a rented buffer.
+ ///
+ /// The value as a
+ public ReadOnlyMemory AsMemory()
+ {
+ this.EnsureRented();
+ return _arrayToReturnToPool.AsMemory(0, _pos);
+ }
+
+ ///
+ /// Returns the contents of the builder as a , renting the underlying buffer if it
+ /// is not already backed by a rented buffer.
+ ///
+ /// The index within the string at which to start.
+ /// The value as a
+ public ReadOnlyMemory AsMemory(int start)
+ {
+ this.EnsureRented();
+ return _arrayToReturnToPool.AsMemory(start, _pos - start);
+ }
+
+ ///
+ /// Returns the contents of the builder as a , renting the underlying buffer if it
+ /// is not already backed by a rented buffer.
+ ///
+ /// The index within the string at which to start.
+ /// The length of the span to return.
+ /// The value as a
+ public ReadOnlyMemory AsMemory(int start, int length)
+ {
+ this.EnsureRented();
+ return _arrayToReturnToPool.AsMemory(start, length);
+ }
+
+ public bool TryCopyTo(Span destination, out int bytesWritten)
+ {
+ if (_bytes.Slice(0, _pos).TryCopyTo(destination))
+ {
+ bytesWritten = _pos;
+ Dispose();
+ return true;
+ }
+ else
+ {
+ bytesWritten = 0;
+ Dispose();
+ return false;
+ }
+ }
+
+ public void Insert(int index, byte value, int count)
+ {
+ if (_pos > _bytes.Length - count)
+ {
+ Grow(count);
+ }
+
+ int remaining = _pos - index;
+ _bytes.Slice(index, remaining).CopyTo(_bytes.Slice(index + count));
+ _bytes.Slice(index, count).Fill(value);
+ _pos += count;
+ }
+
+ public void Insert(int index, scoped ReadOnlySpan s)
+ {
+ int count = s.Length;
+
+ if (_pos > _bytes.Length - count)
+ {
+ Grow(count);
+ }
+
+ int remaining = _pos - index;
+ _bytes.Slice(index, remaining).CopyTo(_bytes.Slice(index + count));
+ s.CopyTo(_bytes.Slice(index));
+ _pos += count;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Append(byte c)
+ {
+ int pos = _pos;
+ Span bytes = _bytes;
+ if ((uint)pos < (uint)bytes.Length)
+ {
+ bytes[pos] = c;
+ _pos = pos + 1;
+ }
+ else
+ {
+ GrowAndAppend(c);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Append(scoped ReadOnlySpan s)
+ {
+ int pos = _pos;
+ if (s.Length == 1 && (uint)pos < (uint)_bytes.Length)
+ {
+ _bytes[pos] = s[0];
+ _pos = pos + 1;
+ }
+ else
+ {
+ AppendSlow(s);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Append(int value, StandardFormat format = default)
+ {
+ const int ExpandInt32String = 14;
+ int bytesWritten;
+ while(!Utf8Formatter.TryFormat(value, _bytes.Slice(_pos), out bytesWritten, format))
+ {
+ Grow(ExpandInt32String);
+ }
+
+ _pos += bytesWritten;
+ }
+
+ private void AppendSlow(scoped ReadOnlySpan s)
+ {
+ int pos = _pos;
+ if (pos > _bytes.Length - s.Length)
+ {
+ Grow(s.Length);
+ }
+
+ s.CopyTo(_bytes.Slice(pos));
+ _pos += s.Length;
+ }
+
+ public void Append(byte c, int count)
+ {
+ if (_pos > _bytes.Length - count)
+ {
+ Grow(count);
+ }
+
+ Span dst = _bytes.Slice(_pos, count);
+ for (int i = 0; i < dst.Length; i++)
+ {
+ dst[i] = c;
+ }
+ _pos += count;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span AppendSpan(int length)
+ {
+ int origPos = _pos;
+ if (origPos > _bytes.Length - length)
+ {
+ Grow(length);
+ }
+
+ _pos = origPos + length;
+ return _bytes.Slice(origPos, length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void GrowAndAppend(byte c)
+ {
+ Grow(1);
+ Append(c);
+ }
+
+ ///
+ /// Resize the internal buffer either by doubling current buffer size or
+ /// by adding to
+ /// whichever is greater.
+ ///
+ ///
+ /// Number of bytes requested beyond current position.
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void Grow(int additionalCapacityBeyondPos)
+ {
+ Debug.Assert(additionalCapacityBeyondPos > 0);
+ Debug.Assert(_bytes.Length < _pos + additionalCapacityBeyondPos, "Grow called incorrectly, no resize is needed.");
+ //Debug.Assert(_pos > _bytes.Length - additionalCapacityBeyondPos, "Grow called incorrectly, no resize is needed.");
+
+ const uint ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength
+
+ // Increase to at least the required size (_pos + additionalCapacityBeyondPos), but try
+ // to double the size if possible, bounding the doubling to not go beyond the max array length.
+ int newCapacity = (int)Math.Max(
+ (uint)(_pos + additionalCapacityBeyondPos),
+ Math.Min((uint)_bytes.Length * 2, ArrayMaxLength));
+
+ // Make sure to let Rent throw an exception if the caller has a bug and the desired capacity is negative.
+ // This could also go negative if the actual required length wraps around.
+ byte[] poolArray = ArrayPool.Shared.Rent(newCapacity);
+
+ _bytes.Slice(0, _pos).CopyTo(poolArray);
+
+ byte[]? toReturn = _arrayToReturnToPool;
+ _bytes = _arrayToReturnToPool = poolArray;
+ if (toReturn != null)
+ {
+ ArrayPool.Shared.Return(toReturn);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Dispose()
+ {
+ byte[]? toReturn = _arrayToReturnToPool;
+ SetToDisposed();
+ if (toReturn != null)
+ {
+ ArrayPool.Shared.Return(toReturn, true);
+ }
+ }
+
+ ///
+ /// Puts the object into a state preventing accidental continued use an a pool already returned
+ /// to an array, or attempting to retrieve information from the object after it has either
+ /// been disposed, or had its buffer returned to the pool as a result of calling
+ /// .
+ ///
+ private void SetToDisposed()
+ {
+ this = default(ValueStringBuilder) with { _pos = -1 };
+ }
+
+ private void EnsureRented()
+ {
+ if (_arrayToReturnToPool == null)
+ {
+ _arrayToReturnToPool = ArrayPool.Shared.Rent(_bytes.Length);
+ _bytes.Slice(0, _pos).CopyTo(_arrayToReturnToPool);
+ _bytes = _arrayToReturnToPool;
+ }
+ }
+}