| File: InternalUtilities\CommandLineUtilities.cs | Web Access |
| Project: src\roslyn\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Text; namespace Roslyn.Utilities { internal static class CommandLineUtilities { /// <summary> /// Split a command line by the same rules as Main would get the commands except the original /// state of backslashes and quotes are preserved. For example in normal Windows command line /// parsing the following command lines would produce equivalent Main arguments: /// /// - /r:a,b /// - /r:"a,b" /// /// This method will differ as the latter will have the quotes preserved. The only case where /// quotes are removed is when the entire argument is surrounded by quotes without any inner /// quotes. /// </summary> /// <remarks> /// Rules for command line parsing, according to MSDN: /// /// Arguments are delimited by white space, which is either a space or a tab. /// /// A string surrounded by double quotation marks ("string") is interpreted /// as a single argument, regardless of white space contained within. /// A quoted string can be embedded in an argument. /// /// A double quotation mark preceded by a backslash (\") is interpreted as a /// literal double quotation mark character ("). /// /// Backslashes are interpreted literally, unless they immediately precede a /// double quotation mark. /// /// If an even number of backslashes is followed by a double quotation mark, /// one backslash is placed in the argv array for every pair of backslashes, /// and the double quotation mark is interpreted as a string delimiter. /// /// If an odd number of backslashes is followed by a double quotation mark, /// one backslash is placed in the argv array for every pair of backslashes, /// and the double quotation mark is "escaped" by the remaining backslash, /// causing a literal double quotation mark (") to be placed in argv. /// </remarks> public static List<string> SplitCommandLineIntoArguments(string commandLine, bool removeHashComments) { return SplitCommandLineIntoArguments(commandLine, removeHashComments, out _); } public static List<string> SplitCommandLineIntoArguments(string commandLine, bool removeHashComments, out char? illegalChar) { var list = new List<string>(); SplitCommandLineIntoArguments(commandLine.AsSpan(), removeHashComments, new StringBuilder(), list, out illegalChar); return list; } public static void SplitCommandLineIntoArguments(ReadOnlySpan<char> commandLine, bool removeHashComments, StringBuilder builder, List<string> list, out char? illegalChar) { var i = 0; builder.Length = 0; illegalChar = null; while (i < commandLine.Length) { while (i < commandLine.Length && char.IsWhiteSpace(commandLine[i])) { i++; } if (i == commandLine.Length) { break; } if (commandLine[i] == '#' && removeHashComments) { break; } var quoteCount = 0; builder.Length = 0; while (i < commandLine.Length && (!char.IsWhiteSpace(commandLine[i]) || (quoteCount % 2 != 0))) { var current = commandLine[i]; switch (current) { case '\\': { var slashCount = 0; do { builder.Append(commandLine[i]); i++; slashCount++; } while (i < commandLine.Length && commandLine[i] == '\\'); // Slashes not followed by a quote character can be ignored for now if (i >= commandLine.Length || commandLine[i] != '"') { break; } // If there is an odd number of slashes then it is escaping the quote // otherwise it is just a quote. if (slashCount % 2 == 0) { quoteCount++; } builder.Append('"'); i++; break; } case '"': builder.Append(current); quoteCount++; i++; break; default: if ((current >= 0x1 && current <= 0x1f) || current == '|') { if (illegalChar == null) { illegalChar = current; } } else { builder.Append(current); } i++; break; } } // If the quote string is surrounded by quotes with no interior quotes then // remove the quotes here. if (quoteCount == 2 && builder[0] == '"' && builder[builder.Length - 1] == '"') { builder.Remove(0, length: 1); builder.Remove(builder.Length - 1, length: 1); } if (builder.Length > 0) { list.Add(builder.ToString()); } } } } }