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 _compiledExpression; private int _expressionDepth; private SearchExpression(List 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 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 query, int expressionDepth) { var compiledExpression = new List(); 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 part, ReadOnlySpan 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 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 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; } }