ParadoxSaveParser/ParadoxSaveParser.Lib/SearchExpression.cs

156 lines
5.2 KiB
C#

namespace ParadoxSaveParser.Lib;
public record SearchArgs
{
public readonly string KeyStr;
public readonly StringBuilder? KeySB;
public readonly int LocalIndex;
public SearchArgs(int localIndex, string keyStr)
{
KeyStr = keyStr;
KeySB = null;
LocalIndex = localIndex;
}
public SearchArgs(int localIndex, StringBuilder keySb)
{
KeyStr = string.Empty;
KeySB = keySb;
LocalIndex = localIndex;
}
}
public interface ISearchExpression
{
bool DoesMatch(SearchArgs args, out ISearchExpression? nextSearchExpression);
}
public static class SearchExpressionCompiler
{
private static bool CharEqualsAndNotEscaped(char c, ReadOnlySpan<char> chars, int i)
=> chars[i] == c && (i < 1 || chars[i - 1] != '\\') && (i < 2 || chars[i - 2] != '\\');
public static ISearchExpression Compile(ReadOnlySpan<char> query)
{
if (query.IsEmpty)
throw new ArgumentNullException(nameof(query));
if (query[0] is '(')
{
var subExprs = new List<ISearchExpression>();
int supExprBegin = 1;
int bracketBalance = 1;
for (int i = supExprBegin; i < query.Length && bracketBalance != 0; i++)
{
if (CharEqualsAndNotEscaped('(', query, i))
{
bracketBalance++;
}
else if (CharEqualsAndNotEscaped(')', query, i))
{
bracketBalance--;
}
else if (bracketBalance == 1 && CharEqualsAndNotEscaped('|', query, i))
{
var subPart = query.Slice(supExprBegin, i - supExprBegin);
var subExpr = Compile(subPart);
subExprs.Add(subExpr);
supExprBegin = i + 1;
}
}
if (query[^1] != ')')
throw new NotImplementedException("Expressions after ')' are not supported");
if (bracketBalance > 0)
throw new Exception("Too many opening brackets");
if (bracketBalance < 0)
throw new Exception("Too many closing brackets");
var subPartLast = query.Slice(supExprBegin, query.Length - supExprBegin - 1);
var subExprLast = Compile(subPartLast);
subExprs.Add(subExprLast);
return new MultipleMatchExpression(subExprs);
}
int partBeforePointLength = 0;
while (partBeforePointLength < query.Length)
{
if (CharEqualsAndNotEscaped('.', query, partBeforePointLength))
break;
partBeforePointLength++;
}
var part = query.Slice(0, partBeforePointLength);
ReadOnlySpan<char> remaining = default;
if (partBeforePointLength < query.Length)
remaining = query.Slice(partBeforePointLength + 1);
if (part is "*") return new AnyMatchExpression(remaining.IsEmpty ? null : Compile(remaining));
for (int j = 0; j < part.Length; j++)
if (CharEqualsAndNotEscaped('*', part, j))
throw new NotImplementedException("pattern matching other than '*' is not implemented yet");
if (part[0] is '[')
{
part = part.Slice(1, part.Length - 2);
return new IndexMatchExpression(int.Parse(part), remaining.IsEmpty ? null : Compile(remaining));
}
return new ExactMatchExpression(part.ToString(), remaining.IsEmpty ? null : Compile(remaining));
}
private record AnyMatchExpression(ISearchExpression? next) : ISearchExpression
{
public bool DoesMatch(SearchArgs args, out ISearchExpression? nextSearchExpression)
{
nextSearchExpression = next;
return true;
}
}
private record MultipleMatchExpression(List<ISearchExpression> subExprs) : ISearchExpression
{
public bool DoesMatch(SearchArgs args, out ISearchExpression? nextSearchExpression)
{
foreach (var e in subExprs)
if (e.DoesMatch(args, out nextSearchExpression))
return true;
nextSearchExpression = null;
return false;
}
}
private record IndexMatchExpression(int index, ISearchExpression? next) : ISearchExpression
{
public bool DoesMatch(SearchArgs args, out ISearchExpression? nextSearchExpression)
{
if (args.LocalIndex == index)
{
nextSearchExpression = next;
return true;
}
nextSearchExpression = null;
return false;
}
}
private record ExactMatchExpression(string key, ISearchExpression? next) : ISearchExpression
{
public bool DoesMatch(SearchArgs args, out ISearchExpression? nextSearchExpression)
{
if ((args.KeySB != null && args.KeySB.Equals(key)) || args.KeyStr == key)
{
nextSearchExpression = next;
return true;
}
nextSearchExpression = null;
return false;
}
}
}