forked from microsoft/qsharp-compiler
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathOptions.cs
318 lines (282 loc) · 13.4 KB
/
Options.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using CommandLine;
using Microsoft.Quantum.QsCompiler.CompilationBuilder;
using Microsoft.Quantum.QsCompiler.DataTypes;
using Microsoft.Quantum.QsCompiler.Diagnostics;
using Microsoft.Quantum.QsCompiler.ReservedKeywords;
using Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.Quantum.QsCompiler.CommandLineCompiler
{
/// <summary>
/// Default values for command line options if nothing is specified.
/// </summary>
internal static class DefaultOptions
{
public const string Verbosity = "normal";
public const Options.LogFormat OutputFormat = Options.LogFormat.Default;
public const int TrimLevel = 1;
}
public class CompilationOptions : Options
{
[Option(
"trim",
Required = false,
Default = DefaultOptions.TrimLevel,
SetName = CODE_MODE,
HelpText = "[Experimental feature] Integer indicating how much to simplify the syntax tree by eliminating selective abstractions.")]
public int TrimLevel { get; set; }
[Option(
"load",
Required = false,
SetName = CODE_MODE,
HelpText = "Path to the .NET Core dll(s) defining additional transformations to include in the compilation process.")]
public IEnumerable<string>? Plugins { get; set; }
[Option(
"target-specific-decompositions",
Required = false,
SetName = CODE_MODE,
HelpText = "[Experimental feature] Path to the .NET Core dll(s) containing target specific implementations.")]
public IEnumerable<string>? TargetSpecificDecompositions { get; set; }
[Option(
"load-test-names",
Required = false,
Default = false,
SetName = CODE_MODE,
HelpText = "Specifies whether public types and callables declared in referenced assemblies are exposed via their test name defined by the corresponding attribute.")]
public bool ExposeReferencesViaTestNames { get; set; }
[Option(
"assembly-properties",
Required = false,
SetName = CODE_MODE,
HelpText = "Additional properties to populate the AssemblyConstants dictionary with. Each item is expected to be of the form \"key:value\".")]
public IEnumerable<string>? AdditionalAssemblyProperties { get; set; }
[Option(
"runtime",
Required = false,
SetName = CODE_MODE,
HelpText = "Specifies the classical capabilites of the runtime. Determines what QIR profile to compile to.")]
public AssemblyConstants.RuntimeCapabilities RuntimeCapabilites { get; set; }
internal RuntimeCapability RuntimeCapability => this.RuntimeCapabilites.ToCapability();
[Option(
"build-exe",
Required = false,
Default = false,
SetName = CODE_MODE,
HelpText = "Specifies whether to build a Q# command line application.")]
public bool MakeExecutable { get; set; }
/// <summary>
/// Returns a dictionary with the specified assembly properties as out parameter.
/// Returns a boolean indicating whether all specified properties were successfully added.
/// </summary>
internal bool ParseAssemblyProperties(out Dictionary<string, string> parsed)
{
var success = true;
parsed = new Dictionary<string, string>();
foreach (var keyValue in this.AdditionalAssemblyProperties ?? Array.Empty<string>())
{
// NB: We use `count: 2` here to ensure that assembly constants can contain colons.
var pieces = keyValue?.Split(":", count: 2);
success =
success && !(pieces is null) && pieces.Length == 2 &&
parsed.TryAdd(pieces[0].Trim().Trim('"'), pieces[1].Trim().Trim('"'));
}
return success;
}
}
public class Options
{
public enum LogFormat
{
Default,
MsBuild
}
// Note: items in one set are mutually exclusive with items from other sets
protected const string CODE_MODE = "codeMode";
protected const string SNIPPET_MODE = "snippetMode";
protected const string RESPONSE_FILES = "responseFiles";
[Option(
'v',
"verbosity",
Required = false,
Default = DefaultOptions.Verbosity,
HelpText = "Specifies the verbosity of the logged output. Valid values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic].")]
public string? Verbosity { get; set; }
[Option(
"format",
Required = false,
Default = DefaultOptions.OutputFormat,
HelpText = "Specifies the output format of the command line compiler.")]
public LogFormat OutputFormat { get; set; }
[Option(
'i',
"input",
Required = true,
SetName = CODE_MODE,
HelpText = "Q# code or name of the Q# file to compile.")]
public IEnumerable<string>? Input { get; set; }
[Option(
's',
"snippet",
Required = true,
SetName = SNIPPET_MODE,
HelpText = "Q# snippet to compile - i.e. Q# code occuring within an operation or function declaration.")]
public string? CodeSnippet { get; set; }
[Option(
'f',
"within-function",
Required = false,
Default = false,
SetName = SNIPPET_MODE,
HelpText = "Specifies whether a given Q# snipped occurs within a function")]
public bool WithinFunction { get; set; }
[Option(
'r',
"references",
Required = false,
Default = new string[0],
HelpText = "Referenced binaries to include in the compilation.")]
public IEnumerable<string>? References { get; set; }
[Option(
'n',
"no-warn",
Required = false,
Default = new int[0],
HelpText = "Warnings with the given code(s) will be ignored.")]
public IEnumerable<int>? NoWarn { get; set; }
[Option(
"package-load-fallback-folders",
Required = false,
SetName = CODE_MODE,
HelpText = "Specifies the directories the compiler will search when a compiler dependency could not be found.")]
public IEnumerable<string>? PackageLoadFallbackFolders { get; set; }
/// <summary>
/// Updates the settings that can be used independent on the other arguments according to the setting in the given options.
/// Already specified non-default values are prioritized over the values in the given options,
/// unless overwriteNonDefaultValues is set to true. Sequences are merged.
/// </summary>
internal void UpdateSetIndependentSettings(Options updates, bool overwriteNonDefaultValues = false)
{
this.Verbosity = overwriteNonDefaultValues || this.Verbosity == DefaultOptions.Verbosity ? updates.Verbosity : this.Verbosity;
this.OutputFormat = overwriteNonDefaultValues || this.OutputFormat == DefaultOptions.OutputFormat ? updates.OutputFormat : this.OutputFormat;
this.NoWarn = (this.NoWarn ?? Array.Empty<int>()).Concat(updates.NoWarn ?? Array.Empty<int>());
this.References = (this.References ?? Array.Empty<string>()).Concat(updates.References ?? Array.Empty<string>());
}
// routines related to logging
/// <summary>
/// If a logger is given, logs the options as CommandLineArguments Information before returning the printed string.
/// </summary>
public string[] Print(ILogger? logger = null)
{
string Value(PropertyInfo p)
{
var v = p.GetValue(this);
return v is string[] a
? string.Join(';', a)
: v?.ToString() ?? "(null)";
}
var props = this.GetType().GetProperties().Where(p => Attribute.IsDefined(p, typeof(OptionAttribute)));
var msg = props.Select(p => $"{p.Name}: {Value(p)}").ToArray();
logger?.Log(InformationCode.CommandLineArguments, Enumerable.Empty<string>(), messageParam: Formatting.Indent(msg).ToArray());
return msg;
}
/// <summary>
/// Given a LogFormat, returns a suitable routing for formatting diagnostics.
/// </summary>
internal static Func<Diagnostic, string> LoggingFormat(LogFormat format) =>
format switch
{
LogFormat.MsBuild => Formatting.MsBuildFormat,
LogFormat.Default => Formatting.HumanReadableFormat,
_ => throw new NotImplementedException("unknown output format for logger"),
};
/// <summary>
/// Creates a suitable logger for the given command line options,
/// logging the given arguments if the verbosity is high enough.
/// </summary>
public ConsoleLogger GetLogger(DiagnosticSeverity defaultVerbosity = DiagnosticSeverity.Warning)
{
var verbosity =
"detailed".Equals(this.Verbosity, StringComparison.InvariantCultureIgnoreCase) ||
"d".Equals(this.Verbosity, StringComparison.InvariantCultureIgnoreCase) ||
"diagnostic".Equals(this.Verbosity, StringComparison.InvariantCultureIgnoreCase) ||
"diag".Equals(this.Verbosity, StringComparison.InvariantCultureIgnoreCase)
? DiagnosticSeverity.Hint :
"quiet".Equals(this.Verbosity, StringComparison.InvariantCultureIgnoreCase) ||
"q".Equals(this.Verbosity, StringComparison.InvariantCultureIgnoreCase)
? DiagnosticSeverity.Error :
defaultVerbosity;
var logger = new ConsoleLogger(
LoggingFormat(this.OutputFormat),
verbosity,
this.NoWarn,
this.CodeSnippet != null ? -2 : 0);
this.Print(logger);
return logger;
}
// routines related to processing snippets
/// <summary>
/// text document identifier used to identify the code snippet in diagnostic mode
/// </summary>
private static readonly Uri SNIPPET_FILE_URI = new Uri(Path.GetFullPath("__CODE_SNIPPET__.qs"));
private static string SNIPPET_FILE_ID =>
QsCompilerError.RaiseOnFailure(
() => CompilationUnitManager.GetFileId(SNIPPET_FILE_URI),
"invalid code snippet id");
/// <summary>
/// name of the namespace within which code snippets are compiled
/// </summary>
private const string SNIPPET_NAMESPACE = "CODE_SNIPPET_NS";
/// <summary>
/// name of the callable within which code snippets are compiled
/// </summary>
private const string SNIPPET_CALLABLE = "CODE_SNIPPET_CALLABLE";
/// <summary>
/// wraps the given content into a namespace and callable that maps Unit to Unit
/// </summary>
public static string AsSnippet(string content, bool inFunction = false) =>
$"{Declarations.Namespace} {SNIPPET_NAMESPACE} {{ \n " +
$"{(inFunction ? Declarations.Function : Declarations.Operation)} {SNIPPET_CALLABLE} () : {Types.Unit} {{ \n" +
$"{content} \n" + // no indentation such that position info is accurate
$"}} \n" +
$"}}";
/// <summary>
/// Helper function that returns true if the given file id is consistent with the one for a code snippet.
/// </summary>
public static bool IsCodeSnippet(string fileId) => fileId == SNIPPET_FILE_ID;
/// <summary>
/// Returns a function that given a routine for loading files from disk,
/// return an enumerable with all text document identifiers and the corresponding file content
/// for the source code or Q# snippet specified by the given options.
/// If both the Input and the CodeSnippet property are set, or none of these properties is set in the given options,
/// logs a suitable error and returns and empty dictionary.
/// </summary>
internal CompilationLoader.SourceLoader LoadSourcesOrSnippet(ILogger logger) => loadFromDisk =>
{
var input = this.Input ?? Enumerable.Empty<string>();
if (this.CodeSnippet == null && input.Any())
{
return loadFromDisk(input);
}
else if (this.CodeSnippet != null && !input.Any())
{
return new Dictionary<Uri, string> { { SNIPPET_FILE_URI, AsSnippet(this.CodeSnippet, this.WithinFunction) } }.ToImmutableDictionary();
}
if (!input.Any())
{
logger?.Log(ErrorCode.MissingInputFileOrSnippet, Enumerable.Empty<string>());
}
else
{
logger?.Log(ErrorCode.SnippetAndInputArguments, Enumerable.Empty<string>());
}
return ImmutableDictionary<Uri, string>.Empty;
};
}
}