/// <summary> /// Returns true if a item function subexpression begins at the specified index /// and ends before the specified end index. /// Leaves index one past the end of the closing paren. /// </summary> private static ItemExpressionCapture SinkItemFunctionExpression(string expression, int startTransform, ref int i, int end) { if (SinkValidName(expression, ref i, end)) { int endFunctionName = i; // Eat any whitespace between the function name and its arguments SinkWhitespace(expression, ref i); int startFunctionArguments = i + 1; if (SinkArgumentsInParentheses(expression, ref i, end)) { int endFunctionArguments = i - 1; ItemExpressionCapture capture = new ItemExpressionCapture(startTransform, i - startTransform, expression.Substring(startTransform, i - startTransform)); capture.FunctionName = expression.Substring(startTransform, endFunctionName - startTransform); if (endFunctionArguments > startFunctionArguments) { capture.FunctionArguments = expression.Substring(startFunctionArguments, endFunctionArguments - startFunctionArguments); } return(capture); } return(null); } else { return(null); } }
/// <summary> /// Given a subexpression, finds referenced item names and inserts them into the table /// as K=Name, V=String.Empty. /// </summary> /// <remarks> /// We can ignore any semicolons in the expression, since we're not itemizing it. /// </remarks> private static void GetReferencedItemNamesAndMetadata(string expression, int start, int end, ref ItemsAndMetadataPair pair, ShredderOptions whatToShredFor) { for (int i = start; i < end; i++) { int restartPoint; if (Sink(expression, ref i, end, '@', '(')) { // Start of a possible item list expression // Store the index to backtrack to if this doesn't turn out to be a well // formed metadata expression. (Subtract one for the increment when we loop around.) restartPoint = i - 1; SinkWhitespace(expression, ref i); int startOfName = i; if (!SinkValidName(expression, ref i, end)) { i = restartPoint; continue; } // '-' is a legitimate char in an item name, but we should match '->' as an arrow // in '@(foo->'x')' rather than as the last char of the item name. // The old regex accomplished this by being "greedy" if (end > i && expression[i - 1] == '-' && expression[i] == '>') { i--; } // Grab the name, but continue to verify it's a well-formed expression // before we store it. string name = expression.Substring(startOfName, i - startOfName); SinkWhitespace(expression, ref i); bool transformOrFunctionFound = true; // If there's an '->' eat it and the subsequent quoted expression or transform function while (Sink(expression, ref i, end, '-', '>') && transformOrFunctionFound) { SinkWhitespace(expression, ref i); int startTransform = i; bool isQuotedTransform = SinkSingleQuotedExpression(expression, ref i, end); if (isQuotedTransform) { continue; } ItemExpressionCapture functionCapture = SinkItemFunctionExpression(expression, startTransform, ref i, end); if (functionCapture != null) { continue; } if (!isQuotedTransform && functionCapture == null) { i = restartPoint; transformOrFunctionFound = false; } } if (!transformOrFunctionFound) { continue; } SinkWhitespace(expression, ref i); // If there's a ',', eat it and the subsequent quoted expression if (Sink(expression, ref i, ',')) { SinkWhitespace(expression, ref i); if (!Sink(expression, ref i, '\'')) { i = restartPoint; continue; } int closingQuote = expression.IndexOf('\'', i); if (closingQuote == -1) { i = restartPoint; continue; } // Look for metadata in the separator expression // e.g., @(foo, '%(bar)') contains batchable metadata 'bar' GetReferencedItemNamesAndMetadata(expression, i, closingQuote, ref pair, ShredderOptions.MetadataOutsideTransforms); i = closingQuote + 1; } SinkWhitespace(expression, ref i); if (!Sink(expression, ref i, ')')) { i = restartPoint; continue; } // If we've got this far, we know the item expression was // well formed, so make sure the name's in the table if ((whatToShredFor & ShredderOptions.ItemTypes) != 0) { pair.Items ??= new HashSet <string>(MSBuildNameIgnoreCaseComparer.Default); pair.Items.Add(name); } i--; continue; } if (Sink(expression, ref i, end, '%', '(')) { // Start of a possible metadata expression // Store the index to backtrack to if this doesn't turn out to be a well // formed metadata expression. (Subtract one for the increment when we loop around.) restartPoint = i - 1; SinkWhitespace(expression, ref i); int startOfText = i; if (!SinkValidName(expression, ref i, end)) { i = restartPoint; continue; } // Grab this, but we don't know if it's an item or metadata name yet string firstPart = expression.Substring(startOfText, i - startOfText); string itemName = null; string metadataName; string qualifiedMetadataName; SinkWhitespace(expression, ref i); bool qualified = Sink(expression, ref i, '.'); if (qualified) { SinkWhitespace(expression, ref i); startOfText = i; if (!SinkValidName(expression, ref i, end)) { i = restartPoint; continue; } itemName = firstPart; metadataName = expression.Substring(startOfText, i - startOfText); qualifiedMetadataName = itemName + "." + metadataName; } else { metadataName = firstPart; qualifiedMetadataName = metadataName; } SinkWhitespace(expression, ref i); if (!Sink(expression, ref i, ')')) { i = restartPoint; continue; } if ((whatToShredFor & ShredderOptions.MetadataOutsideTransforms) != 0) { pair.Metadata ??= new Dictionary <string, MetadataReference>(MSBuildNameIgnoreCaseComparer.Default); pair.Metadata[qualifiedMetadataName] = new MetadataReference(itemName, metadataName); } i--; } } }
/// <summary> /// Returns true if a item function subexpression begins at the specified index /// and ends before the specified end index. /// Leaves index one past the end of the closing paren. /// </summary> private static ItemExpressionCapture SinkItemFunctionExpression(string expression, int startTransform, ref int i, int end) { if (SinkValidName(expression, ref i, end)) { int endFunctionName = i; // Eat any whitespace between the function name and its arguments SinkWhitespace(expression, ref i); int startFunctionArguments = i + 1; if (SinkArgumentsInParentheses(expression, ref i, end)) { int endFunctionArguments = i - 1; ItemExpressionCapture capture = new ItemExpressionCapture(startTransform, i - startTransform, expression.Substring(startTransform, i - startTransform)); capture.FunctionName = expression.Substring(startTransform, endFunctionName - startTransform); if (endFunctionArguments > startFunctionArguments) { capture.FunctionArguments = expression.Substring(startFunctionArguments, endFunctionArguments - startFunctionArguments); } return capture; } return null; } else { return null; } }
/// <summary> /// Given a subexpression, finds referenced sub transform expressions /// itemName and separator will be null if they are not found /// return value will be null if no transform expressions are found /// </summary> internal static List <ItemExpressionCapture> GetReferencedItemExpressions(string expression, int start, int end) { List <ItemExpressionCapture> subExpressions = null; int startIndex = expression.IndexOf('@', start, end - start); if (startIndex < 0) { return(null); } for (int i = startIndex; i < end; i++) { int restartPoint; int startPoint; if (Sink(expression, ref i, end, '@', '(')) { List <ItemExpressionCapture> transformExpressions = null; string separator = null; int separatorStart = -1; // Start of a possible item list expression // Store the index to backtrack to if this doesn't turn out to be a well // formed expression. (Subtract one for the increment when we loop around.) restartPoint = i - 1; // Store the expression's start point startPoint = i - 2; SinkWhitespace(expression, ref i); int startOfName = i; if (!SinkValidName(expression, ref i, end)) { i = restartPoint; continue; } // '-' is a legitimate char in an item name, but we should match '->' as an arrow // in '@(foo->'x')' rather than as the last char of the item name. // The old regex accomplished this by being "greedy" if (end > i && expression[i - 1] == '-' && expression[i] == '>') { i--; } // Grab the name, but continue to verify it's a well-formed expression // before we store it. string name = expression.Substring(startOfName, i - startOfName); // return the item that we're working with string itemName = name; SinkWhitespace(expression, ref i); bool transformOrFunctionFound = true; // If there's an '->' eat it and the subsequent quoted expression or transform function while (Sink(expression, ref i, end, '-', '>') && transformOrFunctionFound) { SinkWhitespace(expression, ref i); int startTransform = i; bool isQuotedTransform = SinkSingleQuotedExpression(expression, ref i, end); if (isQuotedTransform) { int startQuoted = startTransform + 1; int endQuoted = i - 1; if (transformExpressions == null) { transformExpressions = new List <ItemExpressionCapture>(); } transformExpressions.Add(new ItemExpressionCapture(startQuoted, endQuoted - startQuoted, expression.Substring(startQuoted, endQuoted - startQuoted))); continue; } startTransform = i; ItemExpressionCapture functionCapture = SinkItemFunctionExpression(expression, startTransform, ref i, end); if (functionCapture != null) { if (transformExpressions == null) { transformExpressions = new List <ItemExpressionCapture>(); } transformExpressions.Add(functionCapture); continue; } if (!isQuotedTransform && functionCapture == null) { i = restartPoint; transformOrFunctionFound = false; } } if (!transformOrFunctionFound) { continue; } SinkWhitespace(expression, ref i); // If there's a ',', eat it and the subsequent quoted expression if (Sink(expression, ref i, ',')) { SinkWhitespace(expression, ref i); if (!Sink(expression, ref i, '\'')) { i = restartPoint; continue; } int closingQuote = expression.IndexOf('\'', i); if (closingQuote == -1) { i = restartPoint; continue; } separatorStart = i - startPoint; separator = expression.Substring(i, closingQuote - i); i = closingQuote + 1; } SinkWhitespace(expression, ref i); if (!Sink(expression, ref i, ')')) { i = restartPoint; continue; } int endPoint = i; i--; if (subExpressions == null) { subExpressions = new List <ItemExpressionCapture>(); } // Create an expression capture that encompases the entire expression between the @( and the ) // with the item name and any separator contained within it // and each transform expression contained within it (i.e. each ->XYZ) ItemExpressionCapture expressionCapture = new ItemExpressionCapture(startPoint, endPoint - startPoint, expression.Substring(startPoint, endPoint - startPoint), itemName, separator, separatorStart, transformExpressions); subExpressions.Add(expressionCapture); continue; } } return(subExpressions); }
/// <summary> /// Given a subexpression, finds referenced sub transform expressions /// itemName and separator will be null if they are not found /// return value will be null if no transform expressions are found /// </summary> internal static List<ItemExpressionCapture> GetReferencedItemExpressions(string expression, int start, int end) { List<ItemExpressionCapture> subExpressions = null; if (expression.IndexOf('@') < 0) { return null; } for (int i = start; i < end; i++) { int restartPoint; int startPoint; if (Sink(expression, ref i, end, '@', '(')) { List<ItemExpressionCapture> transformExpressions = null; string itemName = null; string separator = null; int separatorStart = -1; int separatorLength = -1; // Start of a possible item list expression // Store the index to backtrack to if this doesn't turn out to be a well // formed expression. (Subtract one for the increment when we loop around.) restartPoint = i - 1; // Store the expression's start point startPoint = i - 2; SinkWhitespace(expression, ref i); int startOfName = i; if (!SinkValidName(expression, ref i, end)) { i = restartPoint; continue; } // '-' is a legitimate char in an item name, but we should match '->' as an arrow // in '@(foo->'x')' rather than as the last char of the item name. // The old regex accomplished this by being "greedy" if (end > i && expression[i - 1] == '-' && expression[i] == '>') { i--; } // Grab the name, but continue to verify it's a well-formed expression // before we store it. string name = expression.Substring(startOfName, i - startOfName); // return the item that we're working with itemName = name; SinkWhitespace(expression, ref i); bool transformOrFunctionFound = true; // If there's an '->' eat it and the subsequent quoted expression or transform function while (Sink(expression, ref i, end, '-', '>') && transformOrFunctionFound) { SinkWhitespace(expression, ref i); int startTransform = i; bool isQuotedTransform = SinkSingleQuotedExpression(expression, ref i, end); if (isQuotedTransform) { int startQuoted = startTransform + 1; int endQuoted = i - 1; if (transformExpressions == null) { transformExpressions = new List<ItemExpressionCapture>(); } transformExpressions.Add(new ItemExpressionCapture(startQuoted, endQuoted - startQuoted, expression.Substring(startQuoted, endQuoted - startQuoted))); continue; } startTransform = i; ItemExpressionCapture functionCapture = SinkItemFunctionExpression(expression, startTransform, ref i, end); if (functionCapture != null) { if (transformExpressions == null) { transformExpressions = new List<ItemExpressionCapture>(); } transformExpressions.Add(functionCapture); continue; } if (!isQuotedTransform && functionCapture == null) { i = restartPoint; transformOrFunctionFound = false; } } if (!transformOrFunctionFound) { continue; } SinkWhitespace(expression, ref i); // If there's a ',', eat it and the subsequent quoted expression if (Sink(expression, ref i, ',')) { SinkWhitespace(expression, ref i); if (!Sink(expression, ref i, '\'')) { i = restartPoint; continue; } int closingQuote = expression.IndexOf('\'', i); if (closingQuote == -1) { i = restartPoint; continue; } separatorStart = i - startPoint; separatorLength = closingQuote - i; separator = expression.Substring(i, separatorLength); i = closingQuote + 1; } SinkWhitespace(expression, ref i); if (!Sink(expression, ref i, ')')) { i = restartPoint; continue; } int endPoint = i; i--; if (subExpressions == null) { subExpressions = new List<ItemExpressionCapture>(); } // Create an expression capture that encompases the entire expression between the @( and the ) // with the item name and any separator contained within it // and each transform expression contained within it (i.e. each ->XYZ) ItemExpressionCapture expressionCapture = new ItemExpressionCapture(startPoint, endPoint - startPoint, expression.Substring(startPoint, endPoint - startPoint), itemName, separator, separatorStart, separatorLength, transformExpressions); subExpressions.Add(expressionCapture); continue; } } return subExpressions; }