Skip to content

Commit 4731fdb

Browse files
Replace command-line parser for Crossgen2 (#43655)
Switch back to the old command-line parser for Crossgen2 to improve performance of parsing arguments.
1 parent 03a2847 commit 4731fdb

23 files changed

+2305
-296
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Collections.ObjectModel;
6+
7+
namespace Internal.CommandLine
8+
{
9+
public abstract class Argument
10+
{
11+
internal Argument(ArgumentCommand command, IEnumerable<string> names, bool isOption, bool isRequired)
12+
{
13+
var nameArray = names.ToArray();
14+
Command = command;
15+
Name = nameArray.First();
16+
Names = new ReadOnlyCollection<string>(nameArray);
17+
IsOption = isOption;
18+
IsRequired = isRequired;
19+
}
20+
21+
public ArgumentCommand Command { get; private set; }
22+
23+
public string Name { get; private set; }
24+
25+
public ReadOnlyCollection<string> Names { get; private set; }
26+
27+
public string Help { get; set; }
28+
29+
public bool IsOption { get; private set; }
30+
31+
public bool IsParameter
32+
{
33+
get { return !IsOption; }
34+
}
35+
36+
public bool IsSpecified { get; private set; }
37+
38+
public bool IsHidden { get; set; }
39+
40+
public bool IsRequired { get; private set; }
41+
42+
public virtual bool IsList
43+
{
44+
get { return false; }
45+
}
46+
47+
public object Value
48+
{
49+
get { return GetValue(); }
50+
}
51+
52+
public object DefaultValue
53+
{
54+
get { return GetDefaultValue(); }
55+
}
56+
57+
public bool IsActive
58+
{
59+
get { return Command == null || Command.IsActive; }
60+
}
61+
62+
public abstract bool IsFlag { get; }
63+
64+
internal abstract object GetValue();
65+
66+
internal abstract object GetDefaultValue();
67+
68+
internal void MarkSpecified()
69+
{
70+
IsSpecified = true;
71+
}
72+
73+
public string GetDisplayName()
74+
{
75+
return GetDisplayName(Name);
76+
}
77+
78+
public IEnumerable<string> GetDisplayNames()
79+
{
80+
return Names.Select(GetDisplayName);
81+
}
82+
83+
private string GetDisplayName(string name)
84+
{
85+
return IsOption ? GetOptionDisplayName(name) : GetParameterDisplayName(name);
86+
}
87+
88+
private static string GetOptionDisplayName(string name)
89+
{
90+
var modifier = name.Length == 1 ? @"-" : @"--";
91+
return modifier + name;
92+
}
93+
94+
private static string GetParameterDisplayName(string name)
95+
{
96+
return @"<" + name + @">";
97+
}
98+
99+
public virtual string GetDisplayValue()
100+
{
101+
return Value == null ? string.Empty : Value.ToString();
102+
}
103+
104+
public override string ToString()
105+
{
106+
return GetDisplayName();
107+
}
108+
}
109+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Internal.CommandLine
5+
{
6+
public abstract class ArgumentCommand
7+
{
8+
internal ArgumentCommand(string name)
9+
{
10+
Name = name;
11+
}
12+
13+
public string Name { get; private set; }
14+
15+
public string Help { get; set; }
16+
17+
public object Value
18+
{
19+
get { return GetValue(); }
20+
}
21+
22+
public bool IsHidden { get; set; }
23+
24+
public bool IsActive { get; private set; }
25+
26+
internal abstract object GetValue();
27+
28+
internal void MarkActive()
29+
{
30+
IsActive = true;
31+
}
32+
33+
public override string ToString()
34+
{
35+
return Name;
36+
}
37+
}
38+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Internal.CommandLine
5+
{
6+
public sealed class ArgumentCommand<T> : ArgumentCommand
7+
{
8+
internal ArgumentCommand(string name, T value)
9+
: base(name)
10+
{
11+
Value = value;
12+
}
13+
14+
public new T Value { get; private set; }
15+
16+
internal override object GetValue()
17+
{
18+
return Value;
19+
}
20+
}
21+
}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
7+
namespace Internal.CommandLine
8+
{
9+
internal static class ArgumentLexer
10+
{
11+
public static IReadOnlyList<ArgumentToken> Lex(IEnumerable<string> args, Func<string, IEnumerable<string>> responseFileReader = null)
12+
{
13+
var result = new List<ArgumentToken>();
14+
15+
// We'll split the arguments into tokens.
16+
//
17+
// A token combines the modifier (/, -, --), the option name, and the option
18+
// value.
19+
//
20+
// Please note that this code doesn't combine arguments. It only provides
21+
// some pre-processing over the arguments to split out the modifier,
22+
// option, and value:
23+
//
24+
// { "--out", "out.exe" } ==> { new ArgumentToken("--", "out", null),
25+
// new ArgumentToken(null, null, "out.exe") }
26+
//
27+
// {"--out:out.exe" } ==> { new ArgumentToken("--", "out", "out.exe") }
28+
//
29+
// The reason it doesn't combine arguments is because it depends on the actual
30+
// definition. For example, if --out is a flag (meaning it's of type bool) then
31+
// out.exe in the first example wouldn't be considered its value.
32+
//
33+
// The code also handles the special -- token which indicates that the following
34+
// arguments shouldn't be considered options.
35+
//
36+
// Finally, this code will also expand any reponse file entries, assuming the caller
37+
// gave us a non-null reader.
38+
39+
var hasSeenDashDash = false;
40+
41+
foreach (var arg in ExpandResponseFiles(args, responseFileReader))
42+
{
43+
// If we've seen a -- already, then we'll treat one as a plain name, that is
44+
// without a modifier or value.
45+
46+
if (!hasSeenDashDash && arg == @"--")
47+
{
48+
hasSeenDashDash = true;
49+
continue;
50+
}
51+
52+
string modifier;
53+
string name;
54+
string value;
55+
56+
if (hasSeenDashDash)
57+
{
58+
modifier = null;
59+
name = arg;
60+
value = null;
61+
}
62+
else
63+
{
64+
// If we haven't seen the -- separator, we're looking for options.
65+
// Options have leading modifiers, i.e. /, -, or --.
66+
//
67+
// Options can also have values, such as:
68+
//
69+
// -f:false
70+
// --name=hello
71+
72+
73+
if (!TryExtractOption(arg, out modifier, out string nameAndValue))
74+
{
75+
name = arg;
76+
value = null;
77+
}
78+
else if (!TrySplitNameValue(nameAndValue, out name, out value))
79+
{
80+
name = nameAndValue;
81+
value = null;
82+
}
83+
}
84+
85+
var token = new ArgumentToken(modifier, name, value);
86+
result.Add(token);
87+
}
88+
89+
// Single letter options can be combined, for example the following two
90+
// forms are considered equivalent:
91+
//
92+
// (1) -xdf
93+
// (2) -x -d -f
94+
//
95+
// In order to free later phases from handling this case, we simply expand
96+
// single letter options to the second form.
97+
98+
for (var i = result.Count - 1; i >= 0; i--)
99+
{
100+
if (IsOptionBundle(result[i]))
101+
ExpandOptionBundle(result, i);
102+
}
103+
104+
return result.ToArray();
105+
}
106+
107+
private static IEnumerable<string> ExpandResponseFiles(IEnumerable<string> args, Func<string, IEnumerable<string>> responseFileReader)
108+
{
109+
foreach (var arg in args)
110+
{
111+
if (responseFileReader == null || !arg.StartsWith(@"@"))
112+
{
113+
yield return arg;
114+
}
115+
else
116+
{
117+
var fileName = arg.Substring(1);
118+
119+
var responseFileArguments = responseFileReader(fileName);
120+
121+
// The reader can suppress expanding this response file by
122+
// returning null. In that case, we'll treat the response
123+
// file token as a regular argument.
124+
125+
if (responseFileArguments == null)
126+
{
127+
yield return arg;
128+
}
129+
else
130+
{
131+
foreach (var responseFileArgument in responseFileArguments)
132+
yield return responseFileArgument.Trim();
133+
}
134+
}
135+
}
136+
}
137+
138+
private static bool IsOptionBundle(ArgumentToken token)
139+
{
140+
return token.IsOption &&
141+
token.Modifier == @"-" &&
142+
token.Name.Length > 1;
143+
}
144+
145+
private static void ExpandOptionBundle(IList<ArgumentToken> receiver, int index)
146+
{
147+
var options = receiver[index].Name;
148+
receiver.RemoveAt(index);
149+
150+
foreach (var c in options)
151+
{
152+
var name = char.ToString(c);
153+
var expandedToken = new ArgumentToken(@"-", name, null);
154+
receiver.Insert(index, expandedToken);
155+
index++;
156+
}
157+
}
158+
159+
private static bool TryExtractOption(string text, out string modifier, out string remainder)
160+
{
161+
return TryExtractOption(text, @"--", out modifier, out remainder) ||
162+
TryExtractOption(text, @"-", out modifier, out remainder);
163+
}
164+
165+
private static bool TryExtractOption(string text, string prefix, out string modifier, out string remainder)
166+
{
167+
if (text.StartsWith(prefix))
168+
{
169+
remainder = text.Substring(prefix.Length);
170+
modifier = prefix;
171+
return true;
172+
}
173+
174+
remainder = null;
175+
modifier = null;
176+
return false;
177+
}
178+
179+
private static bool TrySplitNameValue(string text, out string name, out string value)
180+
{
181+
for (int idx = 0; idx < text.Length; idx++)
182+
{
183+
char ch = text[idx];
184+
if (ch == ':' || ch == '=')
185+
{
186+
name = text.Substring(0, idx);
187+
value = text.Substring(idx + 1);
188+
return true;
189+
}
190+
}
191+
name = null;
192+
value = null;
193+
return false;
194+
}
195+
}
196+
}

0 commit comments

Comments
 (0)