public static StringView SimplifyExpression(StringView outerText, bool trimLastWhiteSpaces = true) { // First we look for the closers that could be trimmed Stack <int> nestedLevelsEnd = new Stack <int>(); Stack <int> nestedLevelsStart = new Stack <int>(); for (int i = outerText.length - 1; i >= 0; --i) { if (char.IsWhiteSpace(outerText[i])) { continue; } if (outerText[i] == '}') { nestedLevelsEnd.Push(i); continue; } break; } // Then we parse the string to check how many we can trim bool hasTrimmedText = false; if (nestedLevelsEnd.Any()) { bool inNonTrimmableText = false; int nonTrimmableOpeners = 0; bool isInString = false; for (int i = 0; i < outerText.length; ++i) { if (char.IsWhiteSpace(outerText[i])) { continue; } if (k_Quotes.Contains(outerText[i])) { isInString = !isInString; } if (isInString) { continue; } if (outerText[i] == '{') { nestedLevelsStart.Push(i); // if part can't be trimmed we must not keep the { so we keep track of how many if (inNonTrimmableText) { ++nonTrimmableOpeners; } continue; } if (!nestedLevelsStart.Any() || !nestedLevelsEnd.Any()) { break; } if (outerText[i] == '}') { if (nonTrimmableOpeners == 0 && i == nestedLevelsEnd.Peek() && nestedLevelsStart.Count == nestedLevelsEnd.Count) { hasTrimmedText = true; break; } nestedLevelsStart.Pop(); if (i == nestedLevelsEnd.Peek()) { nestedLevelsEnd.Pop(); } // In that case that means there was one expression that was closed and no more remaining so we should exit if (!nestedLevelsStart.Any()) { break; } if (inNonTrimmableText && nonTrimmableOpeners > 0) { --nonTrimmableOpeners; } } // if we find other characters that means that part can't be trimmed, in that case we must not keep the next { we might find inNonTrimmableText = true; } } var result = outerText; if (hasTrimmedText) { int start = nestedLevelsStart.Peek() + 1; int length = nestedLevelsEnd.Peek() - start; result = outerText.Substring(start, length); } return(trimLastWhiteSpaces ? result.Trim() : result); }
public static StringView[] GetExpressionsStartAndLength(StringView text, out bool rootHasParameters) { rootHasParameters = false; var openersStack = new Stack <char>(); var expressions = new List <StringView>(); int firstOpenerIndex = -1; int currentStringTokenIndex = -1; for (int i = 0; i < text.length; ++i) { if (text[i] == ' ') { continue; } // In case of a string, we must find the end of the string before checking any nested levels or , if (k_Quotes.Any(c => c == text[i])) { if (currentStringTokenIndex == -1) { currentStringTokenIndex = i; } else if (text[currentStringTokenIndex] != text[i]) { throw new SearchExpressionParseException($"Nested strings are not allowed, \"{text[i]}\" found instead of \"{text[currentStringTokenIndex]}\" in \"{text.Substring(currentStringTokenIndex, i + 1 - currentStringTokenIndex)}\"", text.startIndex + currentStringTokenIndex, i - currentStringTokenIndex + 1); } else { currentStringTokenIndex = -1; } } if (currentStringTokenIndex != -1) // is in string { continue; } if (k_Openers.Any(c => c == text[i])) { if (openersStack.Count == 0) { firstOpenerIndex = i; } openersStack.Push(text[i]); continue; } if (k_Closers.Any(c => c == text[i])) { if (openersStack.Count == 0) { throw new SearchExpressionParseException($"Extra \"{text[i]}\" found", text.startIndex + i, 1); } if (CharMatchOpener(openersStack.Peek(), text[i])) { openersStack.Pop(); // We found the final closer, that means we found the end of the expression if (openersStack.Count == 0) { expressions.Add(text.Substring(firstOpenerIndex, i - firstOpenerIndex + 1)); } continue; } else { throw new SearchExpressionParseException($"Missing \"{GetCorrespondingCloser(openersStack.Peek())}\" in \"{text.Substring(0, i + 1)}\"", text.startIndex + firstOpenerIndex, i - firstOpenerIndex + 1); } } if (openersStack.Count == 0 && text[i] == ',') { rootHasParameters = true; } } if (currentStringTokenIndex != -1) { throw new SearchExpressionParseException($"The string \"{text.Substring(currentStringTokenIndex)}\" is not closed correctly", text.startIndex + currentStringTokenIndex, text.length - currentStringTokenIndex); } if (openersStack.Any()) { throw new SearchExpressionParseException($"Missing \"{GetCorrespondingCloser(openersStack.Peek())}\" in \"{text}\"", text.startIndex + firstOpenerIndex, text.length - firstOpenerIndex); } return(expressions.ToArray()); }
public static bool TryParse(StringView text, out QueryMarker marker) { marker = none; if (!IsQueryMarker(text)) { return(false); } var innerText = text.Substring(k_StartToken.Length, text.Length - k_StartToken.Length - k_EndToken.Length); var indexOfColon = innerText.IndexOf(':'); if (indexOfColon < 0) { return(false); } var controlType = innerText.Substring(0, indexOfColon).Trim().ToString(); var args = new List <StringView>(); var level = 0; var currentArgStart = indexOfColon + 1; for (var i = currentArgStart; i < innerText.Length; ++i) { if (ParserUtils.IsOpener(innerText[i])) { ++level; } if (ParserUtils.IsCloser(innerText[i])) { --level; } if (innerText[i] != ',' || level != 0) { continue; } if (i + 1 == innerText.Length) { return(false); } args.Add(innerText.Substring(currentArgStart, i - currentArgStart).Trim()); currentArgStart = i + 1; } if (currentArgStart == innerText.Length) { return(false); // No arguments } // Extract the final argument, since there is no comma after the last argument args.Add(innerText.Substring(currentArgStart, innerText.Length - currentArgStart).Trim()); var queryMarkerArguments = new List <QueryMarkerArgument>(); using (var context = SearchService.CreateContext("")) { foreach (var arg in args) { var parserArgs = new SearchExpressionParserArgs(arg, context, SearchExpressionParserFlags.ImplicitLiterals); SearchExpression expression = null; try { expression = SearchExpression.Parse(parserArgs); } catch (SearchExpressionParseException) { } if (expression == null || !expression.types.HasAny(SearchExpressionType.Literal)) { queryMarkerArguments.Add(new QueryMarkerArgument { rawText = arg, expression = expression, value = expression == null ? arg.ToString() : null }); continue; } var results = expression.Execute(context); var resolvedValue = results.FirstOrDefault(item => item != null); var resolvedValueObject = resolvedValue?.value; queryMarkerArguments.Add(new QueryMarkerArgument { rawText = arg, expression = expression, value = resolvedValueObject }); } } marker = new QueryMarker { type = controlType, text = text, args = queryMarkerArguments.ToArray() }; return(true); }
public static StringView[] ExtractArguments(StringView text, string errorPrefix = "") { var paramsBlock = text; var expressionParams = new List <StringView>(); var paramStartIndex = -1; var lastCommaIndex = -1; Stack <char> openersStack = new Stack <char>(); openersStack.Push(paramsBlock[0]); int currentStringTokenIndex = -1; var i = 1; for (; i < paramsBlock.length; ++i) { if (paramsBlock[i] == ' ') { continue; } if (paramStartIndex == -1) { paramStartIndex = i; } // In case of a string, we must find the end of the string before checking any nested levels or , if (k_Quotes.Any(c => c == paramsBlock[i])) { if (currentStringTokenIndex == -1) { currentStringTokenIndex = i; } else { currentStringTokenIndex = -1; } } if (currentStringTokenIndex != -1) // is in string { continue; } if (k_Openers.Any(c => c == paramsBlock[i])) { openersStack.Push(paramsBlock[i]); continue; } if (k_Closers.Any(c => c == paramsBlock[i])) { if (CharMatchOpener(openersStack.Peek(), paramsBlock[i])) { openersStack.Pop(); // We found the final closer, that means we found the end of the expression if (openersStack.Count == 0) { var startIndex = GetExpressionStartIndex(paramStartIndex, lastCommaIndex); if (i - startIndex != 0) { expressionParams.Add(paramsBlock.Substring(startIndex, i - startIndex).Trim()); } else if (lastCommaIndex != -1) { throw new SearchExpressionParseException($"Last argument missing in \"{errorPrefix + text.Substring(0, i + 1)}\"", text.startIndex + lastCommaIndex, i - lastCommaIndex + 1); } ++i; break; } continue; } } if (paramsBlock[i] == ',' && openersStack.Count == 1) { var startIndex = GetExpressionStartIndex(paramStartIndex, lastCommaIndex); if (i - startIndex == 0) { var position = lastCommaIndex == -1 ? 0 : lastCommaIndex; throw new SearchExpressionParseException($"The argument is not defined before \",\" in \"{errorPrefix + text.Substring(0, i)}\"", text.startIndex + position, i - position + 1); } lastCommaIndex = i; paramStartIndex = -1; expressionParams.Add(paramsBlock.Substring(startIndex, lastCommaIndex - startIndex).Trim()); } } return(expressionParams.ToArray()); }
public static QueryMarker[] ParseAllMarkers(StringView text) { if (text.Length <= k_StartToken.Length + k_EndToken.Length) { return(null); } List <QueryMarker> markers = new List <QueryMarker>(); var startIndex = -1; var endIndex = -1; var nestedLevel = 0; bool inQuotes = false; for (var i = 0; i < text.Length; ++i) { if (i + k_StartToken.Length > text.Length || i + k_EndToken.Length > text.Length) { break; } if (ParserUtils.IsOpener(text[i]) && !inQuotes) { ++nestedLevel; } if (ParserUtils.IsCloser(text[i]) && !inQuotes) { --nestedLevel; } if (text[i] == '"' || text[i] == '\'') { inQuotes = !inQuotes; } if (startIndex == -1 && nestedLevel == 0 && !inQuotes) { var openerSv = text.Substring(i, k_StartToken.Length); if (openerSv == k_StartToken) { startIndex = i; } } if (endIndex == -1 && nestedLevel == 0 && !inQuotes) { var closerSv = text.Substring(i, k_EndToken.Length); if (closerSv == k_EndToken) { endIndex = i + k_EndToken.Length; } } if (startIndex != -1 && endIndex != -1) { var markerSv = text.Substring(startIndex, endIndex - startIndex); if (TryParse(markerSv, out var marker)) { markers.Add(marker); } startIndex = -1; endIndex = -1; } } return(markers.ToArray()); }