ParadoxSaveParser/ParadoxSaveParser.Lib/SearchExpression.cs

142 lines
4.9 KiB
C#

namespace ParadoxSaveParser.Lib;
public record SearchArgs(string key, int 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.key == key)
{
nextSearchExpression = next;
return true;
}
nextSearchExpression = null;
return false;
}
}
}