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) /// /// /// /// /// Property to modify. /// Value to set to the property. /// Filter Comparison Type public sealed record StringInstruction(string PropertyName, string PropertyValue, InstructionComparer Comparer, InstructionOperation Operation = InstructionOperation.Set) { public string PropertyValue { get; private set; } = PropertyValue; /// /// Sets the to the index of the value in the input , if it exists. /// /// List of values to search for the . /// True if the value was found and set, false otherwise. public bool SetScreenedValue(ReadOnlySpan arr) { int index = arr.IndexOf(PropertyValue); if ((uint)index >= arr.Length) return false; PropertyValue = index.ToString(); return true; } /// /// Valid prefixes that are recognized for value comparison types. /// public static ReadOnlySpan Prefixes => [ Apply, FilterEqual, FilterNotEqual, FilterGreaterThan, FilterGreaterThanOrEqual, FilterLessThan, FilterLessThanOrEqual, ApplyAdd, ApplySubtract, ApplyMultiply, ApplyDivide, ApplyModulo, ApplyBitwiseAnd, ApplyBitwiseOr, ApplyBitwiseXor, ApplyBitwiseShiftRight, ApplyBitwiseShiftLeft, ]; public static bool IsFilterInstruction(char c) => c switch { FilterEqual => true, FilterNotEqual => true, FilterGreaterThan => true, FilterLessThan => true, FilterGreaterThanOrEqual => true, FilterLessThanOrEqual => true, _ => false, }; public static bool IsMutationInstruction(char c) => !IsFilterInstruction(c); private const char Apply = '.'; private const char ApplyAdd = '+'; private const char ApplySubtract = '-'; private const char ApplyMultiply = '*'; private const char ApplyDivide = '/'; private const char ApplyModulo = '%'; private const char ApplyBitwiseAnd = '&'; private const char ApplyBitwiseOr = '|'; private const char ApplyBitwiseXor = '^'; private const char ApplyBitwiseShiftRight = '»'; private const char ApplyBitwiseShiftLeft = '«'; 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; /// /// Apply a instead of fixed value, based on the and values. /// public bool Random { get; private set; } /// /// Gets a value, based on the and values. /// 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); ArgumentOutOfRangeException.ThrowIfNegativeOrZero(index); 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; } } /// /// Gets a list of s from the input . /// public static List GetFilters(ReadOnlySpan text) => GetFilters(text.EnumerateLines()); /// /// Gets a list of filters from the input . /// 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; } /// /// Gets a list of filters from the input . /// 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; } /// /// Gets a list of filters from the input . /// 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; } /// /// Gets a list of filters from the input . /// 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; } /// /// Gets a list of instructions from the input . /// public static List GetInstructions(ReadOnlySpan text) => GetInstructions(text.EnumerateLines()); /// /// Gets a list of instructions from the input . /// 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; } /// /// Gets a list of instructions from the input . /// 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; } /// /// Gets a list of instructions from the input . /// 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; } /// /// Gets a list of instructions from the input . /// 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; } /// /// Tries to parse a filter from the input . /// 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.IsSupported) return false; return TryParseSplitTuple(line[1..], ref entry, comparer); } /// /// Tries to parse a instruction from the input . /// public static bool TryParseInstruction(ReadOnlySpan line, [NotNullWhen(true)] out StringInstruction? entry) { entry = null; if (line.Length is 0 || !TryGetOperation(line[0], out var operation)) return false; return TryParseSplitTuple(line[1..], ref entry, default, operation); } /// /// Tries to split a tuple from the input . /// public static bool TryParseSplitTuple(ReadOnlySpan tuple, [NotNullWhen(true)] ref StringInstruction? entry, InstructionComparer eval = default, InstructionOperation operation = InstructionOperation.Set) { if (!TryParseSplitTuple(tuple, out var name, out var value)) return false; entry = new StringInstruction(name.ToString(), value.ToString(), eval, operation); return true; } /// /// Tries to split a tuple from the input . /// 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); return noExtra == -1; } /// /// Gets the from the input . /// public static InstructionComparer GetComparer(char opCode) => opCode switch { FilterEqual => IsEqual, FilterNotEqual => IsNotEqual, FilterGreaterThan => IsGreaterThan, FilterLessThan => IsLessThan, FilterGreaterThanOrEqual => IsGreaterThanOrEqual, FilterLessThanOrEqual => IsLessThanOrEqual, _ => None, }; /// /// Gets the from the input . /// public static bool TryGetOperation(char opCode, out InstructionOperation operation) { switch (opCode) { case Apply: operation = InstructionOperation.Set; return true; case ApplyAdd: operation = InstructionOperation.Add; return true; case ApplySubtract: operation = InstructionOperation.Subtract; return true; case ApplyMultiply: operation = InstructionOperation.Multiply; return true; case ApplyDivide: operation = InstructionOperation.Divide; return true; case ApplyModulo: operation = InstructionOperation.Modulo; return true; case ApplyBitwiseAnd: operation = InstructionOperation.BitwiseAnd; return true; case ApplyBitwiseOr: operation = InstructionOperation.BitwiseOr; return true; case ApplyBitwiseXor: operation = InstructionOperation.BitwiseXor; return true; case ApplyBitwiseShiftRight: operation = InstructionOperation.BitwiseShiftRight; return true; case ApplyBitwiseShiftLeft: operation = InstructionOperation.BitwiseShiftLeft; return true; default: operation = default; return false; } } }