ParadoxSaveParser/ParadoxSaveParser.Lib/SearchExpression.cs
2025-04-05 02:08:23 +05:00

140 lines
4.5 KiB
C#

using System.Diagnostics;
using System.Linq;
namespace ParadoxSaveParser.Lib;
public record SearchArgs(string key, int currentDepth, int localIndex);
public interface ISearchExpression
{
bool DoesMatch(SearchArgs args);
}
public class SearchExpression : ISearchExpression
{
private List<ISearchExpression> _compiledExpression;
private int _expressionDepth;
private SearchExpression(List<ISearchExpression> compiledExpression, int expressionDepth)
{
_compiledExpression = compiledExpression;
_expressionDepth = expressionDepth;
}
public bool DoesMatch(SearchArgs args)
{
int index = args.currentDepth - _expressionDepth;
if (index < 0 || index >= _compiledExpression.Count)
return true;
return _compiledExpression[index].DoesMatch(args);
}
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 SearchExpression Parse(string query) => ParseInternal(query, 0);
private static SearchExpression ParseInternal(ReadOnlySpan<char> query, int expressionDepth)
{
var compiledExpression = new List<ISearchExpression>();
ISearchExpression exprPart;
int partBegin = 0;
int bracketBalance = 0;
int expressionDepthIncrement = 0;
for (int i = 0; i < query.Length; i++)
{
if (CharEqualsAndNotEscaped('(', query, i))
bracketBalance++;
else if (CharEqualsAndNotEscaped(')', query, i))
bracketBalance--;
else if (bracketBalance == 0 && CharEqualsAndNotEscaped('.', query, i))
{
var part = query.Slice(partBegin, i - partBegin);
expressionDepthIncrement++;
exprPart = ParsePart(part, query, partBegin,
expressionDepth + expressionDepthIncrement);
compiledExpression.Add(exprPart);
partBegin = i + 1;
}
}
exprPart = ParsePart(query.Slice(partBegin), query, partBegin,
expressionDepth + expressionDepthIncrement);
compiledExpression.Add(exprPart);
return new SearchExpression(compiledExpression, expressionDepth);
}
private static ISearchExpression ParsePart(ReadOnlySpan<char> part,
ReadOnlySpan<char> query, int partBegin, int expressionDepth)
{
if (part is "*")
{
return new AnyMatchExpression();
}
if (CharEqualsAndNotEscaped('[', query, partBegin))
{
part = part.Slice(1, part.Length - 2);
return new IndexMatchExpression(int.Parse(part));
}
if(part[0] is '(')
{
var subExprs = new List<ISearchExpression>();
ISearchExpression subExpr;
part = part.Slice(1, part.Length - 2);
int supExprBegin = 0;
for (int j = 0; j < part.Length; j++)
{
if (CharEqualsAndNotEscaped('|', part, j))
{
subExpr = ParseInternal(part.Slice(supExprBegin, j - supExprBegin),
expressionDepth);
subExprs.Add(subExpr);
supExprBegin = j + 1;
}
}
subExpr = ParseInternal(part.Slice(supExprBegin), expressionDepth);
subExprs.Add(subExpr);
return new MultipleMatchExpression(subExprs);
}
return new ExactMatchExpression(part.ToString());
}
private record AnyMatchExpression : ISearchExpression
{
public bool DoesMatch(SearchArgs args) => true;
}
private record MultipleMatchExpression(List<ISearchExpression> subExprs) : ISearchExpression
{
public bool DoesMatch(SearchArgs args)
{
foreach (var e in subExprs)
{
if(e.DoesMatch(args))
return true;
}
return false;
}
}
private record IndexMatchExpression(int index) : ISearchExpression
{
public bool DoesMatch(SearchArgs args) => args.localIndex == index;
}
private record ExactMatchExpression(string key) : ISearchExpression
{
public bool DoesMatch(SearchArgs args) => args.key == key;
}
}