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 chars, int i) => chars[i] == c && (i < 1 || chars[i - 1] != '\\') && (i < 2 || chars[i - 2] != '\\'); public static ISearchExpression Compile(ReadOnlySpan query) { if(query.IsEmpty) throw new ArgumentNullException(nameof(query)); if(query[0] is '(') { var subExprs = new List(); 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 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 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; } } }