Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/Neo.Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public static byte[] HexToBytes(this ReadOnlySpan<char> value)
if (value.IsEmpty)
return [];
if (value.Length % 2 == 1)
throw new FormatException();
throw new FormatException($"value.Length({value.Length}) not multiple of 2");
var result = new byte[value.Length / 2];
for (var i = 0; i < result.Length; i++)
result[i] = byte.Parse(value.Slice(i * 2, 2), NumberStyles.AllowHexSpecifier);
Expand All @@ -169,5 +169,21 @@ public static int GetVarSize(this string value)
var size = value.GetStrictUtf8ByteCount();
return size.GetVarSize() + size;
}

/// <summary>
/// Trims the specified prefix from the start of the <see cref="string"/>, ignoring case.
/// </summary>
/// <param name="value">The <see cref="string"/> to trim.</param>
/// <param name="prefix">The prefix to trim.</param>
/// <returns>
/// The trimmed ReadOnlySpan without prefix. If no prefix is found, the input is returned unmodified.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<char> TrimStartIgnoreCase(this ReadOnlySpan<char> value, ReadOnlySpan<char> prefix)
{
if (value.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase))
return value[prefix.Length..];
return value;
}
}
}
42 changes: 22 additions & 20 deletions src/Neo/UInt160.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,18 +147,6 @@ internal void SafeSerialize(Span<byte> destination)
BinaryPrimitives.WriteUInt32LittleEndian(destination[IxValue3..], _value3);
}

/// <summary>
/// Parses an <see cref="UInt160"/> from the specified <see cref="string"/>.
/// </summary>
/// <param name="value">An <see cref="UInt160"/> represented by a <see cref="string"/>.</param>
/// <returns>The parsed <see cref="UInt160"/>.</returns>
/// <exception cref="FormatException"><paramref name="value"/> is not in the correct format.</exception>
public static UInt160 Parse(string value)
{
if (!TryParse(value, out var result)) throw new FormatException();
return result;
}

public void Serialize(BinaryWriter writer)
{
writer.Write(_value1);
Expand All @@ -171,21 +159,20 @@ public override string ToString()
return "0x" + this.ToArray().ToHexString(reverse: true);
}


/// <summary>
/// Parses an <see cref="UInt160"/> from the specified <see cref="string"/>.
/// </summary>
/// <param name="str">An <see cref="UInt160"/> represented by a <see cref="string"/>.</param>
/// <param name="value">An <see cref="UInt160"/> represented by a <see cref="string"/>.</param>
/// <param name="result">The parsed <see cref="UInt160"/>.</param>
/// <returns><see langword="true"/> if an <see cref="UInt160"/> is successfully parsed; otherwise, <see langword="false"/>.</returns>
public static bool TryParse(string str, [NotNullWhen(true)] out UInt160 result)
/// <returns>
/// <see langword="true"/> if an <see cref="UInt160"/> is successfully parsed; otherwise, <see langword="false"/>.
/// </returns>
public static bool TryParse(string value, [NotNullWhen(true)] out UInt160 result)
{
result = null;
var data = str.AsSpan(); // AsSpan is null safe
if (data.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase))
data = data[2..];

var data = value.AsSpan().TrimStartIgnoreCase("0x");
if (data.Length != Length * 2) return false;

try
{
result = new UInt160(data.HexToBytesReversed());
Expand All @@ -197,6 +184,21 @@ public static bool TryParse(string str, [NotNullWhen(true)] out UInt160 result)
}
}


/// <summary>
/// Parses an <see cref="UInt160"/> from the specified <see cref="string"/>.
/// </summary>
/// <param name="value">An <see cref="UInt160"/> represented by a <see cref="string"/>.</param>
/// <returns>The parsed <see cref="UInt160"/>.</returns>
/// <exception cref="FormatException"><paramref name="value"/> is not in the correct format.</exception>
public static UInt160 Parse(string value)
{
var data = value.AsSpan().TrimStartIgnoreCase("0x");
if (data.Length != Length * 2)
throw new FormatException($"value.Length({data.Length}) != {Length * 2}");
return new UInt160(data.HexToBytesReversed());
}

public static implicit operator UInt160(string s)
{
return Parse(s);
Expand Down
41 changes: 21 additions & 20 deletions src/Neo/UInt256.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,18 +124,6 @@ internal Span<byte> GetSpanLittleEndian()
return buffer; // Keep the same output as Serialize when BigEndian
}

/// <summary>
/// Parses an <see cref="UInt256"/> from the specified <see cref="string"/>.
/// </summary>
/// <param name="value">An <see cref="UInt256"/> represented by a <see cref="string"/>.</param>
/// <returns>The parsed <see cref="UInt256"/>.</returns>
/// <exception cref="FormatException"><paramref name="value"/> is not in the correct format.</exception>
public static UInt256 Parse(string value)
{
if (!TryParse(value, out var result)) throw new FormatException();
return result;
}

public void Serialize(BinaryWriter writer)
{
writer.Write(_value1);
Expand Down Expand Up @@ -183,18 +171,17 @@ public override string ToString()
/// <summary>
/// Parses an <see cref="UInt256"/> from the specified <see cref="string"/>.
/// </summary>
/// <param name="s">An <see cref="UInt256"/> represented by a <see cref="string"/>.</param>
/// <param name="value">An <see cref="UInt256"/> represented by a <see cref="string"/>.</param>
/// <param name="result">The parsed <see cref="UInt256"/>.</param>
/// <returns><see langword="true"/> if an <see cref="UInt256"/> is successfully parsed; otherwise, <see langword="false"/>.</returns>
public static bool TryParse(string s, [NotNullWhen(true)] out UInt256 result)
/// <returns>
/// <see langword="true"/> if an <see cref="UInt256"/> is successfully parsed; otherwise, <see langword="false"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParse(string value, [NotNullWhen(true)] out UInt256 result)
{
result = null;
var data = s.AsSpan(); // AsSpan is null safe
if (data.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase))
data = data[2..];

var data = value.AsSpan().TrimStartIgnoreCase("0x");
if (data.Length != Length * 2) return false;

try
{
result = new UInt256(data.HexToBytesReversed());
Expand All @@ -206,6 +193,20 @@ public static bool TryParse(string s, [NotNullWhen(true)] out UInt256 result)
}
}

/// <summary>
/// Parses an <see cref="UInt256"/> from the specified <see cref="string"/>.
/// </summary>
/// <param name="value">An <see cref="UInt256"/> represented by a <see cref="string"/>.</param>
/// <returns>The parsed <see cref="UInt256"/>.</returns>
/// <exception cref="FormatException"><paramref name="value"/> is not in the correct format.</exception>
public static UInt256 Parse(string value)
{
var data = value.AsSpan().TrimStartIgnoreCase("0x");
if (data.Length != Length * 2)
throw new FormatException($"value.Length({data.Length}) != {Length * 2}");
return new UInt256(data.HexToBytesReversed());
}

public static bool operator ==(UInt256 left, UInt256 right)
{
if (ReferenceEquals(left, right)) return true;
Expand Down
8 changes: 8 additions & 0 deletions tests/Neo.Extensions.Tests/UT_StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ public void TestIsHex()
Assert.IsTrue("".IsHex());
}

[TestMethod]
public void TestTrimStartIgnoreCase()
{
Assert.AreEqual("010203", "0x010203".AsSpan().TrimStartIgnoreCase("0x").ToString());
Assert.AreEqual("010203", "0x010203".AsSpan().TrimStartIgnoreCase("0X").ToString());
Assert.AreEqual("010203", "0X010203".AsSpan().TrimStartIgnoreCase("0x").ToString());
}

[TestMethod]
public void TestGetVarSizeGeneric()
{
Expand Down