Allow negative values to be entered in ram watch for fixed-point watches (#3181)

* structurize NumericStringExtensions better, allow negative fixedpoint numbers

* Tune MaxLength values to make more sense

* Use Invariant culture to prevent ","<->"." issue in decimal recognition for some locales

* Cleanup more watch stuff

- simplify Poke functions by removing redundant text checking
- make fixed-point constants const
- remove OnKeyPress overload; let the user type whatever and check it later, similarly in OnTextChanged
- more invariant NumberFormatInfo use for ","<->"." issues, also do a basic fixup on OnKeyDown
- cleanup ToRawInt and SetFromRawInt, there are likely still issues remaining but I have no idea whether they realistically affect anything with the way those functions are used
- remove now unused functions in NumericStringExtensions
This commit is contained in:
Moritz Bender 2022-03-23 18:19:49 +01:00 committed by GitHub
parent 64ac7253f6
commit 6db532fb84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 92 additions and 539 deletions

View File

@ -1,8 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using BizHawk.Common.StringExtensions;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
namespace BizHawk.Client.Common namespace BizHawk.Client.Common
@ -76,54 +74,14 @@ namespace BizHawk.Client.Common
{ {
try try
{ {
byte val = 0; byte val = Type switch
switch (Type)
{ {
case WatchDisplayType.Unsigned: WatchDisplayType.Unsigned => byte.Parse(value),
if (value.IsUnsigned()) WatchDisplayType.Signed => (byte)sbyte.Parse(value),
{ WatchDisplayType.Hex => byte.Parse(value, NumberStyles.HexNumber),
val = (byte)int.Parse(value); WatchDisplayType.Binary => Convert.ToByte(value, 2),
} _ => 0
else };
{
return false;
}
break;
case WatchDisplayType.Signed:
if (value.IsSigned())
{
val = (byte)(sbyte)int.Parse(value);
}
else
{
return false;
}
break;
case WatchDisplayType.Hex:
if (value.IsHex())
{
val = (byte)int.Parse(value, NumberStyles.HexNumber);
}
else
{
return false;
}
break;
case WatchDisplayType.Binary:
if (value.IsBinary())
{
val = (byte)Convert.ToInt32(value, 2);
}
else
{
return false;
}
break;
}
PokeByte(val); PokeByte(val);
return true; return true;

View File

@ -1,8 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using BizHawk.Common.StringExtensions;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
namespace BizHawk.Client.Common namespace BizHawk.Client.Common
@ -79,77 +77,16 @@ namespace BizHawk.Client.Common
{ {
try try
{ {
uint val = 0; uint val = Type switch
switch (Type)
{ {
case WatchDisplayType.Unsigned: WatchDisplayType.Unsigned => uint.Parse(value),
if (value.IsUnsigned()) WatchDisplayType.Signed => (uint)int.Parse(value),
{ WatchDisplayType.Hex => uint.Parse(value, NumberStyles.HexNumber),
val = (uint)int.Parse(value); WatchDisplayType.FixedPoint_20_12 => (uint)(double.Parse(value, NumberFormatInfo.InvariantInfo) * 4096.0),
} WatchDisplayType.FixedPoint_16_16 => (uint)(double.Parse(value, NumberFormatInfo.InvariantInfo) * 65536.0),
else WatchDisplayType.Float => BitConverter.ToUInt32(BitConverter.GetBytes(float.Parse(value, NumberFormatInfo.InvariantInfo)), 0),
{ _ => 0
return false; };
}
break;
case WatchDisplayType.Signed:
if (value.IsSigned())
{
val = (uint)int.Parse(value);
}
else
{
return false;
}
break;
case WatchDisplayType.Hex:
if (value.IsHex())
{
val = (uint)int.Parse(value, NumberStyles.HexNumber);
}
else
{
return false;
}
break;
case WatchDisplayType.FixedPoint_20_12:
if (value.IsFixedPoint())
{
val = (uint)(int)(double.Parse(value) * 4096.0);
}
else
{
return false;
}
break;
case WatchDisplayType.FixedPoint_16_16:
if (value.IsFixedPoint())
{
val = (uint)(int)(double.Parse(value) * 65536.0);
}
else
{
return false;
}
break;
case WatchDisplayType.Float:
if (value.IsFloat())
{
var bytes = BitConverter.GetBytes(float.Parse(value));
val = BitConverter.ToUInt32(bytes, 0);
}
else
{
return false;
}
break;
}
PokeDWord(val); PokeDWord(val);
return true; return true;
@ -198,7 +135,7 @@ namespace BizHawk.Client.Common
{ {
var bytes = BitConverter.GetBytes(val); var bytes = BitConverter.GetBytes(val);
var _float = BitConverter.ToSingle(bytes, 0); var _float = BitConverter.ToSingle(bytes, 0);
return _float.ToString(); return _float.ToString(NumberFormatInfo.InvariantInfo);
} }
string FormatBinary() string FormatBinary()
@ -217,8 +154,8 @@ namespace BizHawk.Client.Common
WatchDisplayType.Unsigned => val.ToString(), WatchDisplayType.Unsigned => val.ToString(),
WatchDisplayType.Signed => ((int)val).ToString(), WatchDisplayType.Signed => ((int)val).ToString(),
WatchDisplayType.Hex => $"{val:X8}", WatchDisplayType.Hex => $"{val:X8}",
WatchDisplayType.FixedPoint_20_12 => $"{(int)val / 4096.0:0.######}", WatchDisplayType.FixedPoint_20_12 => ((int)val / 4096.0).ToString("0.######", NumberFormatInfo.InvariantInfo),
WatchDisplayType.FixedPoint_16_16 => $"{(int)val / 65536.0:0.######}", WatchDisplayType.FixedPoint_16_16 => ((int)val / 65536.0).ToString("0.######", NumberFormatInfo.InvariantInfo),
WatchDisplayType.Float => FormatFloat(), WatchDisplayType.Float => FormatFloat(),
WatchDisplayType.Binary => FormatBinary(), WatchDisplayType.Binary => FormatBinary(),
_ => val.ToString() _ => val.ToString()

View File

@ -1,8 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using BizHawk.Common.StringExtensions;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
namespace BizHawk.Client.Common namespace BizHawk.Client.Common
@ -74,65 +72,15 @@ namespace BizHawk.Client.Common
{ {
try try
{ {
ushort val = 0; ushort val = Type switch
switch (Type)
{ {
case WatchDisplayType.Unsigned: WatchDisplayType.Unsigned => ushort.Parse(value),
if (value.IsUnsigned()) WatchDisplayType.Signed => (ushort)short.Parse(value),
{ WatchDisplayType.Hex => ushort.Parse(value, NumberStyles.HexNumber),
val = (ushort)int.Parse(value); WatchDisplayType.Binary => Convert.ToUInt16(value, 2),
} WatchDisplayType.FixedPoint_12_4 => (ushort)(double.Parse(value, NumberFormatInfo.InvariantInfo) * 16.0),
else _ => 0
{ };
return false;
}
break;
case WatchDisplayType.Signed:
if (value.IsSigned())
{
val = (ushort)(short)int.Parse(value);
}
else
{
return false;
}
break;
case WatchDisplayType.Hex:
if (value.IsHex())
{
val = (ushort)int.Parse(value, NumberStyles.HexNumber);
}
else
{
return false;
}
break;
case WatchDisplayType.Binary:
if (value.IsBinary())
{
val = (ushort)Convert.ToInt32(value, 2);
}
else
{
return false;
}
break;
case WatchDisplayType.FixedPoint_12_4:
if (value.IsFixedPoint())
{
val = (ushort)(double.Parse(value) * 16.0);
}
else
{
return false;
}
break;
}
PokeWord(val); PokeWord(val);
return true; return true;
@ -183,7 +131,7 @@ namespace BizHawk.Client.Common
_ when !IsValid => "-", _ when !IsValid => "-",
WatchDisplayType.Unsigned => val.ToString(), WatchDisplayType.Unsigned => val.ToString(),
WatchDisplayType.Signed => ((short) val).ToString(), WatchDisplayType.Hex => $"{val:X4}", WatchDisplayType.Signed => ((short) val).ToString(), WatchDisplayType.Hex => $"{val:X4}",
WatchDisplayType.FixedPoint_12_4 => $"{val / 16.0:F4}", WatchDisplayType.FixedPoint_12_4 => ((short)val / 16.0).ToString("F4", NumberFormatInfo.InvariantInfo),
WatchDisplayType.Binary => Convert WatchDisplayType.Binary => Convert
.ToString(val, 2) .ToString(val, 2)
.PadLeft(16, '0') .PadLeft(16, '0')

View File

@ -2,10 +2,8 @@
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using BizHawk.Common.StringExtensions;
using BizHawk.Common.NumberExtensions;
using BizHawk.Client.Common; using BizHawk.Client.Common;
using BizHawk.Common.NumberExtensions;
namespace BizHawk.Client.EmuHawk namespace BizHawk.Client.EmuHawk
{ {
@ -27,7 +25,7 @@ namespace BizHawk.Client.EmuHawk
set set
{ {
var changed = _size != value; var changed = _size != value;
_size = value; _size = value;
if (changed) if (changed)
{ {
@ -87,17 +85,20 @@ namespace BizHawk.Client.EmuHawk
_ => sbyte.MinValue _ => sbyte.MinValue
}; };
private double Max12_4 => MaxUnsignedInt / 16.0; private const double Max12_4 = short.MaxValue / 16.0;
private const double Min12_4 = short.MinValue / 16.0;
private double Max20_12 => MaxUnsignedInt / 4096.0; private const double Max20_12 = int.MaxValue / 4096.0;
private const double Min20_12 = int.MinValue / 4096.0;
private double Max16_16 => MaxUnsignedInt / 65536.0; private const double Max16_16 = int.MaxValue / 65536.0;
private const double Min16_16 = int.MinValue / 65536.0;
private static double _12_4_Unit => 1 / 16.0; private const double _12_4_Unit = 1 / 16.0;
private static double _20_12_Unit => 1 / 4096.0; private const double _20_12_Unit = 1 / 4096.0;
private static double _16_16_Unit => 1 / 65536.0; private const double _16_16_Unit = 1 / 65536.0;
public override void ResetText() public override void ResetText()
{ {
@ -173,88 +174,14 @@ namespace BizHawk.Client.EmuHawk
}; };
break; break;
case WatchDisplayType.FixedPoint_12_4: case WatchDisplayType.FixedPoint_12_4:
MaxLength = 9; MaxLength = 10;
break; break;
case WatchDisplayType.Float: case WatchDisplayType.Float:
MaxLength = 21; MaxLength = 40;
break; break;
case WatchDisplayType.FixedPoint_20_12: case WatchDisplayType.FixedPoint_20_12:
case WatchDisplayType.FixedPoint_16_16: case WatchDisplayType.FixedPoint_16_16:
MaxLength = 64; MaxLength = 24;
break;
}
}
protected override void OnKeyPress(KeyPressEventArgs e)
{
if (e.KeyChar == '\b' || e.KeyChar == 22 || e.KeyChar == 1 || e.KeyChar == 3)
{
return;
}
if (e.KeyChar == '.')
{
if (Text.Contains(".") && !SelectedText.Contains("."))
{
e.Handled = true;
return;
}
}
else if (e.KeyChar == '-')
{
if (Text.Contains("-") && !SelectedText.Contains("-"))
{
e.Handled = true;
return;
}
}
switch (_type)
{
default:
case WatchDisplayType.Binary:
if (!e.KeyChar.IsBinary())
{
e.Handled = true;
}
break;
case WatchDisplayType.FixedPoint_12_4:
case WatchDisplayType.FixedPoint_20_12:
case WatchDisplayType.FixedPoint_16_16:
if (!e.KeyChar.IsFixedPoint())
{
e.Handled = true;
}
break;
case WatchDisplayType.Float:
if (!e.KeyChar.IsFloat())
{
e.Handled = true;
}
break;
case WatchDisplayType.Hex:
if (!e.KeyChar.IsHex())
{
e.Handled = true;
}
break;
case WatchDisplayType.Signed:
if (!e.KeyChar.IsSigned())
{
e.Handled = true;
}
break;
case WatchDisplayType.Unsigned:
if (!e.KeyChar.IsUnsigned())
{
e.Handled = true;
}
break; break;
} }
} }
@ -321,47 +248,47 @@ namespace BizHawk.Client.EmuHawk
Text = hexVal.ToHexString(MaxLength); Text = hexVal.ToHexString(MaxLength);
break; break;
case WatchDisplayType.FixedPoint_12_4: case WatchDisplayType.FixedPoint_12_4:
var f12val = double.Parse(text); var f12val = double.Parse(text, NumberFormatInfo.InvariantInfo);
if (f12val > Max12_4 - _12_4_Unit) if (f12val > Max12_4 - _12_4_Unit)
{ {
f12val = 0; f12val = Min12_4;
} }
else else
{ {
f12val += _12_4_Unit; f12val += _12_4_Unit;
} }
Text = f12val.ToString(); Text = f12val.ToString(NumberFormatInfo.InvariantInfo);
break; break;
case WatchDisplayType.FixedPoint_20_12: case WatchDisplayType.FixedPoint_20_12:
var f24val = double.Parse(text); var f20val = double.Parse(text, NumberFormatInfo.InvariantInfo);
if (f24val >= Max20_12 - _20_12_Unit) if (f20val > Max20_12 - _20_12_Unit)
{ {
f24val = 0; f20val = Min20_12;
} }
else else
{ {
f24val += _20_12_Unit; f20val += _20_12_Unit;
} }
Text = f24val.ToString(); Text = f20val.ToString(NumberFormatInfo.InvariantInfo);
break; break;
case WatchDisplayType.FixedPoint_16_16: case WatchDisplayType.FixedPoint_16_16:
var f16val = double.Parse(text); var f16val = double.Parse(text, NumberFormatInfo.InvariantInfo);
if (f16val >= Max16_16 - _16_16_Unit) if (f16val > Max16_16 - _16_16_Unit)
{ {
f16val = 0; f16val = Min16_16;
} }
else else
{ {
f16val += _16_16_Unit; f16val += _16_16_Unit;
} }
Text = f16val.ToString(); Text = f16val.ToString(NumberFormatInfo.InvariantInfo);
break; break;
case WatchDisplayType.Float: case WatchDisplayType.Float:
var dVal = double.Parse(text); var dVal = double.Parse(text, NumberFormatInfo.InvariantInfo);
if (dVal > double.MaxValue - 1) if (dVal > float.MaxValue - 1)
{ {
dVal = 0; dVal = 0;
} }
@ -370,7 +297,7 @@ namespace BizHawk.Client.EmuHawk
dVal++; dVal++;
} }
Text = dVal.ToString(); Text = dVal.ToString(NumberFormatInfo.InvariantInfo);
break; break;
} }
} }
@ -437,8 +364,8 @@ namespace BizHawk.Client.EmuHawk
Text = hexVal.ToHexString(MaxLength); Text = hexVal.ToHexString(MaxLength);
break; break;
case WatchDisplayType.FixedPoint_12_4: case WatchDisplayType.FixedPoint_12_4:
var f12val = double.Parse(text); var f12val = double.Parse(text, NumberFormatInfo.InvariantInfo);
if (f12val < 0 + _12_4_Unit) if (f12val < Min12_4 + _12_4_Unit)
{ {
f12val = Max12_4; f12val = Max12_4;
} }
@ -447,24 +374,24 @@ namespace BizHawk.Client.EmuHawk
f12val -= _12_4_Unit; f12val -= _12_4_Unit;
} }
Text = f12val.ToString(); Text = f12val.ToString(NumberFormatInfo.InvariantInfo);
break; break;
case WatchDisplayType.FixedPoint_20_12: case WatchDisplayType.FixedPoint_20_12:
var f24val = double.Parse(text); var f20val = double.Parse(text, NumberFormatInfo.InvariantInfo);
if (f24val < 0 + _20_12_Unit) if (f20val < Min20_12 + _20_12_Unit)
{ {
f24val = Max20_12; f20val = Max20_12;
} }
else else
{ {
f24val -= _20_12_Unit; f20val -= _20_12_Unit;
} }
Text = f24val.ToString(); Text = f20val.ToString(NumberFormatInfo.InvariantInfo);
break; break;
case WatchDisplayType.FixedPoint_16_16: case WatchDisplayType.FixedPoint_16_16:
var f16val = double.Parse(text); var f16val = double.Parse(text, NumberFormatInfo.InvariantInfo);
if (f16val < 0 + _16_16_Unit) if (f16val < Min16_16 + _16_16_Unit)
{ {
f16val = Max16_16; f16val = Max16_16;
} }
@ -473,11 +400,11 @@ namespace BizHawk.Client.EmuHawk
f16val -= _16_16_Unit; f16val -= _16_16_Unit;
} }
Text = f16val.ToString(); Text = f16val.ToString(NumberFormatInfo.InvariantInfo);
break; break;
case WatchDisplayType.Float: case WatchDisplayType.Float:
var dval = double.Parse(text); var dval = double.Parse(text, NumberFormatInfo.InvariantInfo);
if (dval > double.MaxValue - 1) if (dval < float.MinValue + 1)
{ {
dval = 0; dval = 0;
} }
@ -486,7 +413,7 @@ namespace BizHawk.Client.EmuHawk
dval--; dval--;
} }
Text = dval.ToString(); Text = dval.ToString(NumberFormatInfo.InvariantInfo);
break; break;
} }
} }
@ -505,113 +432,32 @@ namespace BizHawk.Client.EmuHawk
return; return;
} }
switch (_type)
{
case WatchDisplayType.Signed:
Text = Text.OnlySigned();
break;
case WatchDisplayType.Unsigned:
Text = Text.OnlyUnsigned();
break;
case WatchDisplayType.Binary:
Text = Text.OnlyBinary();
break;
case WatchDisplayType.Hex:
Text = Text.OnlyHex();
break;
case WatchDisplayType.FixedPoint_12_4:
case WatchDisplayType.FixedPoint_20_12:
case WatchDisplayType.FixedPoint_16_16:
Text = Text.OnlyFixedPoint();
break;
case WatchDisplayType.Float:
Text = Text.OnlyFloat();
break;
}
base.OnTextChanged(e); base.OnTextChanged(e);
} }
public int? ToRawInt() public int? ToRawInt()
{ {
if (string.IsNullOrWhiteSpace(Text)) try
{ {
if (Nullable) return _type switch
{ {
return null; WatchDisplayType.Signed => int.Parse(Text),
} WatchDisplayType.Unsigned => (int)uint.Parse(Text),
WatchDisplayType.Binary => Convert.ToInt32(Text, 2),
return 0; WatchDisplayType.Hex => int.Parse(Text, NumberStyles.HexNumber),
WatchDisplayType.FixedPoint_12_4 => (int)(double.Parse(Text, NumberFormatInfo.InvariantInfo) * 16.0),
WatchDisplayType.FixedPoint_20_12 => (int)(double.Parse(Text, NumberFormatInfo.InvariantInfo) * 4096.0),
WatchDisplayType.FixedPoint_16_16 => (int)(double.Parse(Text, NumberFormatInfo.InvariantInfo) * 65536.0),
WatchDisplayType.Float => BitConverter.ToInt32(BitConverter.GetBytes(float.Parse(Text, NumberFormatInfo.InvariantInfo)), 0),
_ => int.Parse(Text)
};
} }
catch
switch (_type)
{ {
case WatchDisplayType.Signed: // ignored
if (Text.IsSigned())
{
return Text == "-" ? 0 : int.Parse(Text);
}
break;
case WatchDisplayType.Unsigned:
if (Text.IsUnsigned())
{
return (int)uint.Parse(Text);
}
break;
case WatchDisplayType.Binary:
if (Text.IsBinary())
{
return Convert.ToInt32(Text, 2);
}
break;
case WatchDisplayType.Hex:
if (Text.IsHex())
{
return int.Parse(Text, NumberStyles.HexNumber);
}
break;
case WatchDisplayType.FixedPoint_12_4:
if (Text.IsFixedPoint())
{
return (int)(double.Parse(Text) * 16.0);
}
break;
case WatchDisplayType.FixedPoint_20_12:
if (Text.IsFixedPoint())
{
return (int)(double.Parse(Text) * 4096.0);
}
break;
case WatchDisplayType.FixedPoint_16_16:
if (Text.IsFixedPoint())
{
return (int)(double.Parse(Text) * 65536.0);
}
break;
case WatchDisplayType.Float:
if (Text.IsFloat())
{
if (Text == "-" || Text == ".")
{
return 0;
}
float val = float.Parse(Text);
var bytes = BitConverter.GetBytes(val);
return BitConverter.ToInt32(bytes, 0);
}
break;
} }
return 0; return Nullable ? null : 0;
} }
public void SetFromRawInt(int? val) public void SetFromRawInt(int? val)
@ -637,18 +483,18 @@ namespace BizHawk.Client.EmuHawk
Text = val.Value.ToHexString(MaxLength); Text = val.Value.ToHexString(MaxLength);
break; break;
case WatchDisplayType.FixedPoint_12_4: case WatchDisplayType.FixedPoint_12_4:
Text = $"{val.Value / 16.0:F5}"; Text = (val.Value / 16.0).ToString("F5", NumberFormatInfo.InvariantInfo);
break; break;
case WatchDisplayType.FixedPoint_20_12: case WatchDisplayType.FixedPoint_20_12:
Text = $"{val.Value / 4096.0:F5}"; Text = (val.Value / 4096.0).ToString("F5", NumberFormatInfo.InvariantInfo);
break; break;
case WatchDisplayType.FixedPoint_16_16: case WatchDisplayType.FixedPoint_16_16:
Text = $"{val.Value / 65536.0:F5}"; Text = (val.Value / 65536.0).ToString("F5", NumberFormatInfo.InvariantInfo);
break; break;
case WatchDisplayType.Float: case WatchDisplayType.Float:
var bytes = BitConverter.GetBytes(val.Value); var bytes = BitConverter.GetBytes(val.Value);
float _float = BitConverter.ToSingle(bytes, 0); float _float = BitConverter.ToSingle(bytes, 0);
Text = $"{_float:F6}"; Text = _float.ToString("F6", NumberFormatInfo.InvariantInfo);
break; break;
} }
} }

View File

@ -1,5 +1,4 @@
using System.Linq; using System.Linq;
using System.Text;
namespace BizHawk.Common.StringExtensions namespace BizHawk.Common.StringExtensions
{ {
@ -13,26 +12,6 @@ namespace BizHawk.Common.StringExtensions
/// <remarks><paramref name="str"/> should exclude the prefix <c>0b</c></remarks> /// <remarks><paramref name="str"/> should exclude the prefix <c>0b</c></remarks>
public static bool IsBinary(this string? str) => !string.IsNullOrWhiteSpace(str) && str.All(IsBinary); public static bool IsBinary(this string? str) => !string.IsNullOrWhiteSpace(str) && str.All(IsBinary);
/// <returns><see langword="true"/> iff <paramref name="c"/> is <c>'.'</c> or a digit</returns>
/// <remarks>Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be <c>IsUnsignedDecimal</c>.</remarks>
public static bool IsFixedPoint(this char c) => IsUnsigned(c) || c == '.';
/// <returns>
/// <see langword="true"/> iff <paramref name="str"/> is not <see langword="null"/>,<br/>
/// all chars of <paramref name="str"/> are <c>'.'</c> or a digit, and<br/>
/// <paramref name="str"/> contains at most <c>1</c> decimal separator <c>'.'</c>
/// </returns>
/// <remarks>
/// <paramref name="str"/> should exclude the suffix <c>M</c>.<br/>
/// This method returning <see langword="true"/> for some <paramref name="str"/> does not imply that <see cref="float.TryParse(string,out float)">float.TryParse</see> will also return <see langword="true"/>.<br/>
/// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be <c>IsUnsignedDecimal</c>.
/// </remarks>
public static bool IsFixedPoint(this string? str) => !string.IsNullOrWhiteSpace(str) && str.Count(c => c == '.') <= 1 && str.All(IsFixedPoint);
/// <returns><see langword="true"/> iff <paramref name="c"/> is <c>'-'</c>, <c>'.'</c>, or a digit</returns>
/// <remarks>Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be <c>IsSignedDecimal</c>.</remarks>
public static bool IsFloat(this char c) => c.IsFixedPoint() || c == '-';
/// <returns><see langword="true"/> iff <paramref name="c"/> is a hex digit (<c>[0-9A-Fa-f]</c>)</returns> /// <returns><see langword="true"/> iff <paramref name="c"/> is a hex digit (<c>[0-9A-Fa-f]</c>)</returns>
public static bool IsHex(this char c) => IsUnsigned(c) || 'A' <= char.ToUpperInvariant(c) && char.ToUpperInvariant(c) <= 'F'; public static bool IsHex(this char c) => IsUnsigned(c) || 'A' <= char.ToUpperInvariant(c) && char.ToUpperInvariant(c) <= 'F';
@ -46,128 +25,13 @@ namespace BizHawk.Common.StringExtensions
/// <returns><see langword="true"/> iff <paramref name="c"/> is a digit</returns> /// <returns><see langword="true"/> iff <paramref name="c"/> is a digit</returns>
public static bool IsUnsigned(this char c) => char.IsDigit(c); public static bool IsUnsigned(this char c) => char.IsDigit(c);
/// <returns><see langword="true"/> iff <paramref name="str"/> is not <see langword="null"/> and all chars of <paramref name="str"/> are digits</returns>
public static bool IsUnsigned(this string? str) => !string.IsNullOrWhiteSpace(str) && str.All(IsUnsigned);
/// <returns>
/// A copy of <paramref name="raw"/> with characters removed so that the whole thing passes <see cref="IsBinary(string?)">IsBinary</see>.<br/>
/// That is, all chars of the copy will be either <c>'0'</c> or <c>'1'</c>.
/// </returns>
public static string OnlyBinary(this string? raw) => string.IsNullOrWhiteSpace(raw) ? string.Empty : string.Concat(raw.Where(IsBinary));
/// <returns> /// <returns>
/// A copy of <paramref name="raw"/> with characters removed so that the whole thing passes <see cref="IsHex(string?)">IsHex</see>.<br/> /// A copy of <paramref name="raw"/> with characters removed so that the whole thing passes <see cref="IsHex(string?)">IsHex</see>.<br/>
/// That is, all chars of the copy will be hex digits (<c>[0-9A-F]</c>). /// That is, all chars of the copy will be hex digits (<c>[0-9A-F]</c>).
/// </returns> /// </returns>
public static string OnlyHex(this string? raw) => string.IsNullOrWhiteSpace(raw) ? string.Empty : string.Concat(raw.Where(IsHex)).ToUpperInvariant(); public static string OnlyHex(this string? raw) => string.IsNullOrWhiteSpace(raw) ? string.Empty : string.Concat(raw.Where(IsHex)).ToUpperInvariant();
/// <returns> /// <returns><see langword="true"/> iff <paramref name="str"/> is not <see langword="null"/> and all chars of <paramref name="str"/> are digits</returns>
/// A copy of <paramref name="raw"/> with characters removed so that the whole thing passes <see cref="IsUnsigned(string?)">IsUnsigned</see>.<br/> public static bool IsUnsigned(this string? str) => !string.IsNullOrWhiteSpace(str) && str.All(IsUnsigned);
/// That is, all chars of the copy will be digits.
/// </returns>
public static string OnlyUnsigned(this string? raw) => string.IsNullOrWhiteSpace(raw) ? string.Empty : string.Concat(raw.Where(IsUnsigned));
#pragma warning disable CS8602
/// <returns>
/// <see langword="true"/> iff <paramref name="str"/> is not <see langword="null"/>,<br/>
/// the first char of <paramref name="str"/> is <c>'-'</c>, <c>'.'</c>, or a digit,<br/>
/// all subsequent chars of <paramref name="str"/> are <c>'.'</c> or a digit, and<br/>
/// <paramref name="str"/> contains at most <c>1</c> decimal separator <c>'.'</c>
/// </returns>
/// <remarks>
/// <paramref name="str"/> should exclude the suffix <c>f</c>.<br/>
/// This method returning <see langword="true"/> for some <paramref name="str"/> does not imply that <see cref="float.TryParse(string,out float)">float.TryParse</see> will also return <see langword="true"/>.<br/>
/// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be <c>IsSignedDecimal</c>.
/// </remarks>
public static bool IsFloat(this string? str) => !string.IsNullOrWhiteSpace(str) && str.Count(c => c == '.') <= 1 && str[0].IsFloat() && str.Substring(1).All(IsFixedPoint);
/// <returns>
/// <see langword="true"/> iff <paramref name="str"/> is not <see langword="null"/>,
/// the first char of <paramref name="str"/> is <c>'-'</c> or a digit, and
/// all subsequent chars of <paramref name="str"/> are digits
/// </returns>
public static bool IsSigned(this string? str) => !string.IsNullOrWhiteSpace(str) && str[0].IsSigned() && str.Substring(1).All(IsUnsigned);
/// <returns>
/// A copy of <paramref name="raw"/> with characters removed so that the whole thing passes <see cref="IsFixedPoint(string?)">IsFixedPoint</see>.<br/>
/// That is, the all chars of the copy will be <c>'.'</c> or a digit and the copy will contain at most <c>1</c> decimal separator <c>'.'</c>.
/// </returns>
/// <remarks>
/// The returned value may not be parseable by <see cref="float.TryParse(string,out float)">float.TryParse</see>.<br/>
/// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be <c>IsUnsignedDecimal</c>.
/// </remarks>
public static string OnlyFixedPoint(this string? raw)
{
if (string.IsNullOrWhiteSpace(raw)) return string.Empty;
var output = new StringBuilder();
var usedDot = false;
foreach (var chr in raw)
{
if (chr == '.')
{
if (usedDot) continue;
output.Append(chr);
usedDot = true;
}
else if (chr.IsUnsigned()) output.Append(chr);
}
return output.ToString();
}
/// <returns>
/// A copy of <paramref name="raw"/> with characters removed so that the whole thing passes <see cref="IsFloat(string?)">IsFloat</see>.<br/>
/// That is, the first char of the copy will be <c>'-'</c>, <c>'.'</c>, or a digit,<br/>
/// all subsequent chars of the copy will be <c>'.'</c> or a digit, and<br/>
/// the copy will contain at most <c>1</c> decimal separator <c>'.'</c>.
/// </returns>
/// <remarks>
/// If <paramref name="raw"/> contains a serialized negative decimal, it must be at the start (<paramref name="raw"/><c>[0] == '-'</c>) or the sign will be dropped.<br/>
/// The returned value may not be parseable by <see cref="float.TryParse(string,out float)">float.TryParse</see>.<br/>
/// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be <c>IsSignedDecimal</c>.
/// </remarks>
public static string OnlyFloat(this string? raw)
{
if (string.IsNullOrWhiteSpace(raw)) return string.Empty;
var output = new StringBuilder();
var usedDot = false;
var first = raw[0];
if (first.IsFloat())
{
output.Append(first);
if (first == '.') usedDot = true;
}
for (int i = 1, l = raw.Length; i < l; i++)
{
var chr = raw[i];
if (chr == '.')
{
if (usedDot) continue;
output.Append(chr);
usedDot = true;
}
else if (chr.IsUnsigned()) output.Append(chr);
}
return output.ToString();
}
/// <returns>
/// A copy of <paramref name="raw"/> with characters removed so that the whole thing passes <see cref="IsSigned(string?)">IsSigned</see>.<br/>
/// That is, the first char of the copy will be <c>'-'</c> or a digit, and all subsequent chars of the copy will be digits.
/// </returns>
/// <remarks>If <paramref name="raw"/> contains a serialized negative integer, it must be at the start (<paramref name="raw"/><c>[0] == '-'</c>) or the sign will be dropped.</remarks>
public static string OnlySigned(this string? raw)
{
if (string.IsNullOrWhiteSpace(raw)) return string.Empty;
return raw[0].IsSigned()
? raw[0] + string.Concat(raw.Skip(1).Where(IsUnsigned))
: string.Concat(raw.Skip(1).Where(IsUnsigned));
}
#pragma warning restore CS8602
} }
} }