using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Text; using static PKHeX.Core.InstructionComparer; namespace PKHeX.Core; /// /// Batch Editing instruction /// /// /// Can be a filter (skip), or a modification instruction (modify) /// /// /// /// public sealed class StringInstruction { /// Property to modify. public string PropertyName { get; } /// Value to set to the property. public string PropertyValue { get; private set; } /// Filter Comparison Type public InstructionComparer Comparer { get; private init; } public StringInstruction(string name, string value) { PropertyName = name; PropertyValue = value; } public void SetScreenedValue(ReadOnlySpan arr) { int index = arr.IndexOf(PropertyValue); if ((uint)index < arr.Length) PropertyValue = index.ToString(); } public static readonly IReadOnlyList Prefixes = new[] { Apply, FilterEqual, FilterNotEqual, FilterGreaterThan, FilterGreaterThanOrEqual, FilterLessThan, FilterLessThanOrEqual }; private const char Apply = '.'; private const char SplitRange = ','; private const char FilterEqual = '='; private const char FilterNotEqual = '!'; private const char FilterGreaterThan = '>'; private const char FilterLessThan = '<'; private const char FilterGreaterThanOrEqual = '≥'; private const char FilterLessThanOrEqual = '≤'; /// /// Character which divides a property and a value. /// /// /// Example: /// =Species=1 /// The second = is the split. /// public const char SplitInstruction = '='; // Extra Functionality private int RandomMinimum, RandomMaximum; public bool Random { get; private set; } public int RandomValue => Util.Rand.Next(RandomMinimum, RandomMaximum + 1); /// /// Checks if the input is a valid "random range" specification. /// public static bool IsRandomRange(ReadOnlySpan str) { // Need at least one character on either side of the splitter char. int index = str.IndexOf(SplitRange); return index > 0 && index < str.Length - 1; } /// /// Sets a "random range" specification to the instruction. /// /// When the splitter is not present. public void SetRandomRange(ReadOnlySpan str) { var index = str.IndexOf(SplitRange); if (index <= 0) throw new ArgumentException($"Invalid Random Range: {str.ToString()}", nameof(str)); var min = str[..index]; var max = str[(index + 1)..]; _ = int.TryParse(min, out RandomMinimum); _ = int.TryParse(max, out RandomMaximum); if (RandomMinimum == RandomMaximum) { PropertyValue = RandomMinimum.ToString(); Debug.WriteLine($"{PropertyName} randomization range Min/Max same?"); } else { Random = true; } } public static List GetFilters(ReadOnlySpan text) => GetFilters(text.EnumerateLines()); public static List GetFilters(ReadOnlySpan lines) { var result = new List(lines.Length); foreach (var line in lines) { if (TryParseFilter(line, out var entry)) result.Add(entry); } return result; } public static List GetFilters(SpanLineEnumerator lines) { var result = new List(); foreach (var line in lines) { if (TryParseFilter(line, out var entry)) result.Add(entry); } return result; } public static List GetFilters(IReadOnlyList lines) { var result = new List(lines.Count); foreach (var line in lines) { if (TryParseFilter(line, out var entry)) result.Add(entry); } return result; } public static List GetFilters(IEnumerable lines) { var result = new List(); foreach (var line in lines) { if (TryParseFilter(line, out var entry)) result.Add(entry); } return result; } public static List GetInstructions(ReadOnlySpan text) => GetInstructions(text.EnumerateLines()); public static List GetInstructions(ReadOnlySpan lines) { var result = new List(lines.Length); foreach (var line in lines) { if (TryParseInstruction(line, out var entry)) result.Add(entry); } return result; } public static List GetInstructions(SpanLineEnumerator lines) { var result = new List(); foreach (var line in lines) { if (TryParseInstruction(line, out var entry)) result.Add(entry); } return result; } public static List GetInstructions(IReadOnlyList lines) { var result = new List(lines.Count); foreach (var line in lines) { if (TryParseInstruction(line, out var entry)) result.Add(entry); } return result; } public static List GetInstructions(IEnumerable lines) { var result = new List(); foreach (var line in lines) { if (TryParseInstruction(line, out var entry)) result.Add(entry); } return result; } public static bool TryParseFilter(ReadOnlySpan line, [NotNullWhen(true)] out StringInstruction? entry) { entry = null; if (line.Length is 0) return false; var comparer = GetComparer(line[0]); if (!comparer.IsSupportedComparer()) return false; return TryParseSplitTuple(line[1..], ref entry, comparer); } public static bool TryParseInstruction(ReadOnlySpan line, [NotNullWhen(true)] out StringInstruction? entry) { entry = null; if (line.Length is 0 || line[0] is not Apply) return false; return TryParseSplitTuple(line[1..], ref entry); } public static bool TryParseSplitTuple(ReadOnlySpan tuple, [NotNullWhen(true)] ref StringInstruction? entry, InstructionComparer eval = default) { if (!TryParseSplitTuple(tuple, out var name, out var value)) return false; entry = new StringInstruction(name.ToString(), value.ToString()) { Comparer = eval }; return true; } public static bool TryParseSplitTuple(ReadOnlySpan tuple, out ReadOnlySpan name, out ReadOnlySpan value) { name = default; value = default; var splitIndex = tuple.IndexOf(SplitInstruction); if (splitIndex <= 0) return false; name = tuple[..splitIndex]; if (name.IsWhiteSpace()) return false; value = tuple[(splitIndex + 1)..]; var noExtra = value.IndexOf(SplitInstruction); if (noExtra != -1) return false; return true; } public static InstructionComparer GetComparer(char c) => c switch { FilterEqual => IsEqual, FilterNotEqual => IsNotEqual, FilterGreaterThan => IsGreaterThan, FilterLessThan => IsLessThan, FilterGreaterThanOrEqual => IsGreaterThanOrEqual, FilterLessThanOrEqual => IsLessThanOrEqual, _ => None, }; } /// /// Value comparison type /// public enum InstructionComparer : byte { None, IsEqual, IsNotEqual, IsGreaterThan, IsGreaterThanOrEqual, IsLessThan, IsLessThanOrEqual, } public static class InstructionComparerExtensions { /// /// Indicates if the is supported by the logic. /// /// Type of comparison requested /// True if supported, false if unsupported. public static bool IsSupportedComparer(this InstructionComparer comparer) => comparer switch { IsEqual => true, IsNotEqual => true, IsGreaterThan => true, IsGreaterThanOrEqual => true, IsLessThan => true, IsLessThanOrEqual => true, _ => false, }; /// /// Checks if the compare operator is satisfied by a boolean comparison result. /// /// Type of comparison requested /// Result from Equals comparison /// True if satisfied /// Only use this method if the comparison is boolean only. Use the otherwise. public static bool IsCompareEquivalence(this InstructionComparer comparer, bool compareResult) => comparer switch { IsEqual => compareResult, IsNotEqual => !compareResult, _ => false, }; /// /// Checks if the compare operator is satisfied by the result. /// /// Type of comparison requested /// Result from CompareTo /// True if satisfied public static bool IsCompareOperator(this InstructionComparer comparer, int compareResult) => comparer switch { IsEqual => compareResult is 0, IsNotEqual => compareResult is not 0, IsGreaterThan => compareResult > 0, IsGreaterThanOrEqual => compareResult >= 0, IsLessThan => compareResult < 0, IsLessThanOrEqual => compareResult <= 0, _ => false, }; }