diff --git a/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs b/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs index 54efe03736d..4f12bc51f3a 100644 --- a/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs +++ b/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs @@ -106,6 +106,15 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation AcceptUnit(u : Unit) : Unit { + return u; + } +} + +// --- + namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { @EntryPoint() operation AcceptStringArray(xs : String[]) : String[] { @@ -117,8 +126,44 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { @EntryPoint() - operation AcceptUnit(u : Unit) : Unit { - return u; + operation AcceptBigIntArray(bs : BigInt[]) : BigInt[] { + return bs; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation AcceptPauliArray(ps : Pauli[]) : Pauli[] { + return ps; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation AcceptRangeArray(rs : Range[]) : Range[] { + return rs; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation AcceptResultArray(rs : Result[]) : Result[] { + return rs; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation AcceptUnitArray(us : Unit[]) : Unit[] { + return us; } } diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index 038acd5d4f0..0e93aeb32e5 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -224,6 +224,10 @@ let ``Accepts Pauli`` () = given ["-p"; "PauliX"] |> yields "PauliX" given ["-p"; "PauliY"] |> yields "PauliY" given ["-p"; "PauliZ"] |> yields "PauliZ" + given ["-p"; "paulii"] |> yields "PauliI" + given ["-p"; "paulix"] |> yields "PauliX" + given ["-p"; "pauliy"] |> yields "PauliY" + given ["-p"; "pauliz"] |> yields "PauliZ" given ["-p"; "PauliW"] |> fails [] @@ -266,31 +270,79 @@ let ``Accepts String`` () = given ["-s"; "Hello, World!"] |> yields "Hello, World!" [] -let ``Accepts String array`` () = +let ``Accepts Unit`` () = let given = test 12 + given ["-u"; "()"] |> yields "" + given ["-u"; "42"] |> fails + +[] +let ``Accepts String array`` () = + let given = test 13 given ["--xs"; "foo"] |> yields "[foo]" given ["--xs"; "foo"; "bar"] |> yields "[foo,bar]" given ["--xs"; "foo bar"; "baz"] |> yields "[foo bar,baz]" given ["--xs"; "foo"; "bar"; "baz"] |> yields "[foo,bar,baz]" + given ["--xs"] |> fails [] -let ``Accepts Unit`` () = +let ``Accepts BigInt array`` () = + let given = test 14 + given ["--bs"; "9223372036854775808"] |> yields "[9223372036854775808]" + given ["--bs"; "1"; "2"; "9223372036854775808"] |> yields "[1,2,9223372036854775808]" + given ["--bs"; "1"; "2"; "4.2"] |> fails + +[] +let ``Accepts Pauli array`` () = + let given = test 15 + given ["--ps"; "PauliI"] |> yields "[PauliI]" + given ["--ps"; "PauliZ"; "PauliX"] |> yields "[PauliZ,PauliX]" + given ["--ps"; "PauliY"; "PauliY"; "PauliY"] |> yields "[PauliY,PauliY,PauliY]" + given ["--ps"; "PauliY"; "PauliZ"; "PauliW"] |> fails + +[] +let ``Accepts Range array`` () = + let given = test 16 + given ["--rs"; "1..10"] |> yields "[1..1..10]" + given ["--rs"; "1..10"; "2..4..20"] |> yields "[1..1..10,2..4..20]" + given ["--rs"; "1 ..10"; "2 ..4 ..20"] |> yields "[1..1..10,2..4..20]" + given ["--rs"; "1 .. 10"; "2 .. 4 .. 20"] |> yields "[1..1..10,2..4..20]" + given ["--rs"; "1 .. 1o"; "2 .. 4 .. 20"] |> fails + +[] +let ``Accepts Result array`` () = + let given = test 17 + given ["--rs"; "One"] |> yields "[One]" + given ["--rs"; "One"; "Zero"] |> yields "[One,Zero]" + given ["--rs"; "Zero"; "One"; "Two"] |> fails + +[] +let ``Accepts Unit array`` () = + let given = test 18 + given ["--us"; "()"] |> yields "[()]" + given ["--us"; "()"; "()"] |> yields "[(),()]" + given ["--us"; "()"; "()"; "()"] |> yields "[(),(),()]" + given ["--us"; "()"; "unit"; "()"] |> fails + +[] +let ``Supports repeat-name array syntax`` () = let given = test 13 - given ["-u"; "()"] |> yields "" - given ["-u"; "42"] |> fails + given ["--xs"; "Hello"; "--xs"; "World"] |> yields "[Hello,World]" + given ["--xs"; "foo"; "bar"; "--xs"; "baz"] |> yields "[foo,bar,baz]" + given ["--xs"; "foo"; "--xs"; "bar"; "--xs"; "baz"] |> yields "[foo,bar,baz]" + given ["--xs"; "foo bar"; "--xs"; "baz"] |> yields "[foo bar,baz]" // Multiple Options [] let ``Accepts two options`` () = - let given = test 14 + let given = test 19 given ["-n"; "7"; "-b"; "true"] |> yields "7 True" given ["-b"; "true"; "-n"; "7"] |> yields "7 True" [] let ``Accepts three options`` () = - let given = test 15 + let given = test 20 given ["-n"; "7"; "-b"; "true"; "--xs"; "foo"] |> yields "7 True [foo]" given ["--xs"; "foo"; "-n"; "7"; "-b"; "true"] |> yields "7 True [foo]" given ["-n"; "7"; "--xs"; "foo"; "-b"; "true"] |> yields "7 True [foo]" @@ -298,7 +350,7 @@ let ``Accepts three options`` () = [] let ``Requires all options`` () = - let given = test 15 + let given = test 20 given ["-b"; "true"; "--xs"; "foo"] |> fails given ["-n"; "7"; "--xs"; "foo"] |> fails given ["-n"; "7"; "-b"; "true"] |> fails @@ -312,13 +364,13 @@ let ``Requires all options`` () = [] let ``Uses kebab-case`` () = - let given = test 16 + let given = test 21 given ["--camel-case-name"; "foo"] |> yields "foo" given ["--camelCaseName"; "foo"] |> fails [] let ``Uses single-dash short names`` () = - let given = test 17 + let given = test 22 given ["-x"; "foo"] |> yields "foo" given ["--x"; "foo"] |> fails @@ -327,7 +379,7 @@ let ``Uses single-dash short names`` () = [] let ``Shadows --simulator`` () = - let given = test 18 + let given = test 23 given ["--simulator"; "foo"] |> yields "foo" given ["--simulator"; AssemblyConstants.ResourcesEstimator] |> yields AssemblyConstants.ResourcesEstimator given ["-s"; AssemblyConstants.ResourcesEstimator; "--simulator"; "foo"] |> fails @@ -335,14 +387,14 @@ let ``Shadows --simulator`` () = [] let ``Shadows -s`` () = - let given = test 19 + let given = test 24 given ["-s"; "foo"] |> yields "foo" given ["--simulator"; AssemblyConstants.ToffoliSimulator; "-s"; "foo"] |> yields "foo" given ["--simulator"; "bar"; "-s"; "foo"] |> fails [] let ``Shadows --version`` () = - let given = test 20 + let given = test 25 given ["--version"; "foo"] |> yields "foo" @@ -361,30 +413,30 @@ BorrowedWidth 0" [] let ``Supports QuantumSimulator`` () = - let given = test 21 + let given = test 26 given ["--simulator"; AssemblyConstants.QuantumSimulator; "--use-h"; "false"] |> yields "Hello, World!" given ["--simulator"; AssemblyConstants.QuantumSimulator; "--use-h"; "true"] |> yields "Hello, World!" [] let ``Supports ToffoliSimulator`` () = - let given = test 21 + let given = test 26 given ["--simulator"; AssemblyConstants.ToffoliSimulator; "--use-h"; "false"] |> yields "Hello, World!" given ["--simulator"; AssemblyConstants.ToffoliSimulator; "--use-h"; "true"] |> fails [] let ``Supports ResourcesEstimator`` () = - let given = test 21 + let given = test 26 given ["--simulator"; AssemblyConstants.ResourcesEstimator; "--use-h"; "false"] |> yields resourceSummary given ["--simulator"; AssemblyConstants.ResourcesEstimator; "--use-h"; "true"] |> yields resourceSummary [] let ``Rejects unknown simulator`` () = - let given = test 21 + let given = test 26 given ["--simulator"; "FooSimulator"; "--use-h"; "false"] |> fails [] let ``Supports default standard simulator`` () = - let given = testWith 21 AssemblyConstants.ResourcesEstimator + let given = testWith 26 AssemblyConstants.ResourcesEstimator given ["--use-h"; "false"] |> yields resourceSummary given ["--simulator"; AssemblyConstants.QuantumSimulator; "--use-h"; "false"] |> yields "Hello, World!" @@ -392,7 +444,7 @@ let ``Supports default standard simulator`` () = let ``Supports default custom simulator`` () = // This is not really a "custom" simulator, but the driver does not recognize the fully-qualified name of the // standard simulators, so it is treated as one. - let given = testWith 21 typeof.FullName + let given = testWith 26 typeof.FullName given ["--use-h"; "false"] |> yields "Hello, World!" given ["--use-h"; "true"] |> fails given ["--simulator"; typeof.FullName; "--use-h"; "false"] |> yields "Hello, World!" @@ -425,7 +477,7 @@ Options: Commands: simulate (default) Run the program using a local simulator." - let given = test 22 + let given = test 27 given ["--help"] |> yields message given ["-h"] |> yields message given ["-?"] |> yields message diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 0d060066d23..7a2148ea449 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -1,4 +1,7 @@ -module Microsoft.Quantum.QsCompiler.CsharpGeneration.EntryPoint +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +module Microsoft.Quantum.QsCompiler.CsharpGeneration.EntryPoint open Microsoft.CodeAnalysis.CSharp open Microsoft.CodeAnalysis.CSharp.Syntax @@ -45,36 +48,52 @@ let private constantsClass = constant "ResourcesEstimator" "string" (``literal`` AssemblyConstants.ResourcesEstimator)] ``}`` +/// The name of the C# type used by the parameter in its command-line option, given its Q# type. +let rec private csharpParameterTypeName context (qsType : ResolvedType) = + match qsType.Resolution with + | ArrayType itemType -> sprintf "System.Collections.Generic.IEnumerable<%s>" + (csharpParameterTypeName context itemType) + | _ -> SimulationCode.roslynTypeName context qsType + /// A sequence of all of the named parameters in the argument tuple and their respective C# and Q# types. let rec private parameters context doc = function | QsTupleItem variable -> - match variable.VariableName, variable.Type.Resolution with - | ValidName name, ArrayType itemType -> - // Command-line parsing libraries can't convert to IQArray. Use IEnumerable instead. - let typeName = sprintf "System.Collections.Generic.IEnumerable<%s>" - (SimulationCode.roslynTypeName context itemType) - Seq.singleton { Name = name.Value - QsharpType = variable.Type - CsharpTypeName = typeName - Description = ParameterDescription doc name.Value } - | ValidName name, _ -> + match variable.VariableName with + | ValidName name -> Seq.singleton { Name = name.Value QsharpType = variable.Type - CsharpTypeName = SimulationCode.roslynTypeName context variable.Type + CsharpTypeName = csharpParameterTypeName context variable.Type Description = ParameterDescription doc name.Value } - | InvalidName, _ -> Seq.empty + | InvalidName -> Seq.empty | QsTuple items -> items |> Seq.map (parameters context doc) |> Seq.concat -/// The custom argument handler for the given Q# type. -let private argumentHandler = - // TODO: Support array types derived from types that need a custom argument handler. - function - | UnitType -> Some "UnitArgumentHandler" - | Result -> Some "ResultArgumentHandler" - | BigInt -> Some "BigIntArgumentHandler" - | Range -> Some "RangeArgumentHandler" - | _ -> None - >> Option.map (fun handler -> ``ident`` "Driver" <|.|> ``ident`` handler) +/// The argument parser for the Q# type. +let private argumentParser qsType = + let rec valueParser = function + | ArrayType (itemType : ResolvedType) -> valueParser itemType.Resolution + | BigInt -> Some "TryParseBigInteger" + | Range -> Some "TryParseQRange" + | Result -> Some "TryParseResult" + | UnitType -> Some "TryParseQVoid" + | _ -> None + let argParser = + match qsType with + | ArrayType _ -> "ParseArgumentsWith" + | _ -> "ParseArgumentWith" + valueParser qsType |> Option.map (fun valueParser -> + ``ident`` "Parsers" <.> (``ident`` argParser, [``ident`` "Parsers" <|.|> ``ident`` valueParser])) + +/// Adds suggestions, if any, to the option based on the Q# type. +let private withSuggestions qsType option = + let rec suggestions = function + | ArrayType (itemType : ResolvedType) -> suggestions itemType.Resolution + | Result -> ["Zero"; "One"] + | _ -> [] + match suggestions qsType with + | [] -> option + | suggestions -> + let args = option :: List.map ``literal`` suggestions + ``invoke`` (``ident`` "System.CommandLine.OptionExtensions.WithSuggestions") ``(`` args ``)`` /// A property containing a sequence of command-line options corresponding to each parameter given. let private parameterOptionsProperty parameters = @@ -86,16 +105,16 @@ let private parameterOptionsProperty parameters = if name.Length = 1 then ``literal`` ("-" + name) else ``literal`` "--" <+> ``invoke`` toKebabCaseIdent ``(`` [``literal`` name] ``)`` - let members = - argumentHandler qsType.Resolution - |> Option.map (fun handler -> ``ident`` "Argument" <-- handler :> ExpressionSyntax) - |> Option.toList - |> List.append [``ident`` "Required" <-- ``true``] + let args = + match argumentParser qsType.Resolution with + | Some parser -> [nameExpr; parser; upcast ``false``; ``literal`` desc] + | None -> [nameExpr; ``literal`` desc] - ``new init`` (``type`` [sprintf "%s<%s>" optionTypeName typeName]) ``(`` [nameExpr; ``literal`` desc] ``)`` + ``new init`` (``type`` [sprintf "%s<%s>" optionTypeName typeName]) ``(`` args ``)`` ``{`` - members + [``ident`` "Required" <-- ``true``] ``}`` + |> withSuggestions qsType.Resolution let options = parameters |> Seq.map getOption |> Seq.toList ``property-arrow_get`` optionsEnumerableTypeName "Options" [``public``; ``static``] @@ -116,11 +135,10 @@ let private runMethod context (entryPoint : QsCallable) = let taskTypeName = sprintf "System.Threading.Tasks.Task<%s>" returnTypeName let factoryParamName = "__factory__" - let getArgExpr { Name = name; QsharpType = qsType } = + let argExpr { Name = name; QsharpType = qsType } = let property = ``ident`` "this" <|.|> ``ident`` (parameterPropertyName name) match qsType.Resolution with | ArrayType itemType -> - // Convert the IEnumerable property into a QArray. let arrayTypeName = sprintf "QArray<%s>" (SimulationCode.roslynTypeName context itemType) ``new`` (``type`` arrayTypeName) ``(`` [property] ``)`` | _ -> property @@ -128,7 +146,7 @@ let private runMethod context (entryPoint : QsCallable) = let callArgs : seq = Seq.concat [ Seq.singleton (upcast ``ident`` factoryParamName) - Seq.map getArgExpr (parameters context entryPoint.Documentation entryPoint.ArgumentTuple) + Seq.map argExpr (parameters context entryPoint.Documentation entryPoint.ArgumentTuple) ] ``arrow_method`` taskTypeName "Run" ``<<`` [] ``>>`` @@ -197,10 +215,12 @@ let private generatedClasses context (entryPoint : QsCallable) = /// The source code for the entry point driver. let private driver (entryPoint : QsCallable) = - let name = "Microsoft.Quantum.CsharpGeneration.Resources.EntryPointDriver.cs" - use stream = Assembly.GetExecutingAssembly().GetManifestResourceStream name - use reader = new StreamReader(stream) - reader.ReadToEnd().Replace("@Namespace", generatedNamespace entryPoint.FullName.Namespace.Value) + let source fileName = + let resourceName = "Microsoft.Quantum.CsharpGeneration.Resources.EntryPoint." + fileName + use stream = Assembly.GetExecutingAssembly().GetManifestResourceStream resourceName + use reader = new StreamReader(stream) + reader.ReadToEnd().Replace("@Namespace", generatedNamespace entryPoint.FullName.Namespace.Value) + String.Join (Environment.NewLine, source "Driver.cs", source "Parsers.cs", source "Validation.cs") /// Generates C# source code for a standalone executable that runs the Q# entry point. let generate context entryPoint = diff --git a/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj b/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj index 88c0c4e8082..0a279b90245 100644 --- a/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj +++ b/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj @@ -8,7 +8,9 @@ - + + + @@ -28,5 +30,5 @@ - + diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs new file mode 100644 index 00000000000..d461e0f133e --- /dev/null +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs @@ -0,0 +1,209 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace @Namespace +{ + using Microsoft.Quantum.Simulation.Core; + using Microsoft.Quantum.Simulation.Simulators; + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.CommandLine; + using System.CommandLine.Builder; + using System.CommandLine.Help; + using System.CommandLine.Invocation; + using System.CommandLine.Parsing; + using System.Linq; + using System.Threading.Tasks; + + /// + /// The entry point driver is the entry point for the C# application that executes the Q# entry point. + /// + internal static class Driver + { + /// + /// A modification of the command line class. + /// + private sealed class QsHelpBuilder : HelpBuilder + { + public QsHelpBuilder(IConsole console) : base(console) { } + + protected override string ArgumentDescriptor(IArgument argument) + { + // Hide long argument descriptors. + var descriptor = base.ArgumentDescriptor(argument); + return descriptor.Length > 30 ? argument.Name : descriptor; + } + } + + /// + /// Runs the entry point. + /// + /// The command-line arguments. + /// The exit code. + private static async Task Main(string[] args) + { + var simulate = new Command("simulate", "(default) Run the program using a local simulator."); + TryCreateOption( + Constants.SimulatorOptions, + () => EntryPoint.DefaultSimulator, + "The name of the simulator to use.").Then(option => + { + option.Argument.AddSuggestions(ImmutableHashSet.Empty + .Add(Constants.QuantumSimulator) + .Add(Constants.ToffoliSimulator) + .Add(Constants.ResourcesEstimator) + .Add(EntryPoint.DefaultSimulator)); + simulate.AddOption(option); + }); + simulate.Handler = CommandHandler.Create(Simulate); + + var root = new RootCommand(EntryPoint.Summary) { simulate }; + foreach (var option in EntryPoint.Options) { root.AddGlobalOption(option); } + + // Set the simulate command as the default. + foreach (var option in simulate.Options) { root.AddOption(option); } + root.Handler = simulate.Handler; + + return await new CommandLineBuilder(root) + .UseDefaults() + .UseHelpBuilder(context => new QsHelpBuilder(context.Console)) + .Build() + .InvokeAsync(args); + } + + /// + /// Simulates the entry point. + /// + /// The entry point. + /// The simulator to use. + /// The exit code. + private static async Task Simulate(EntryPoint entryPoint, string simulator) + { + simulator = DefaultIfShadowed(Constants.SimulatorOptions.First(), simulator, EntryPoint.DefaultSimulator); + switch (simulator) + { + case Constants.ResourcesEstimator: + var resourcesEstimator = new ResourcesEstimator(); + await entryPoint.Run(resourcesEstimator); + Console.WriteLine(resourcesEstimator.ToTSV()); + break; + default: + var (isCustom, createSimulator) = simulator switch + { + Constants.QuantumSimulator => + (false, new Func(() => new QuantumSimulator())), + Constants.ToffoliSimulator => + (false, new Func(() => new ToffoliSimulator())), + _ => (true, EntryPoint.CreateDefaultCustomSimulator) + }; + if (isCustom && simulator != EntryPoint.DefaultSimulator) + { + DisplayCustomSimulatorError(simulator); + return 1; + } + await DisplayEntryPointResult(entryPoint, createSimulator); + break; + } + return 0; + } + + /// + /// Runs the entry point on a simulator and displays its return value. + /// + /// The entry point. + /// A function that creates an instance of the simulator to use. + private static async Task DisplayEntryPointResult( + EntryPoint entryPoint, Func createSimulator) + { + var simulator = createSimulator(); + try + { + var value = await entryPoint.Run(simulator); +#pragma warning disable CS0184 + if (!(value is QVoid)) +#pragma warning restore CS0184 + { + Console.WriteLine(value); + } + } + finally + { + if (simulator is IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + /// + /// Returns true if the alias is available for use by the driver (that is, the alias is not already used by an + /// entry point option). + /// + /// The alias to check. + /// True if the alias is available for use by the driver. + private static bool IsAliasAvailable(string alias) => + !EntryPoint.Options.SelectMany(option => option.RawAliases).Contains(alias); + + /// + /// Returns the default value and displays a warning if the alias is shadowed by an entry point option, and + /// returns the original value otherwise. + /// + /// The type of the values. + /// The primary option alias corresponding to the value. + /// The value of the option given on the command line. + /// The default value for the option. + /// + private static T DefaultIfShadowed(string alias, T value, T defaultValue) + { + if (IsAliasAvailable(alias)) + { + return value; + } + else + { + var originalForeground = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Error.WriteLine($"Warning: Option {alias} is overriden by an entry point parameter name."); + Console.Error.WriteLine($" Using default value {defaultValue}."); + Console.ForegroundColor = originalForeground; + return defaultValue; + } + } + + /// + /// Tries to create an option by removing aliases that are already in use by the entry point. If the first + /// alias, which is considered the primary alias, is in use, then the option is not created. + /// + /// The type of the option's argument. + /// The option's aliases. + /// A function that returns the option's default value. + /// The option's description. + /// A validation of the option. + private static Validation> TryCreateOption( + IEnumerable aliases, Func getDefaultValue, string description = null) => + IsAliasAvailable(aliases.First()) + ? Validation>.Success( + new Option(aliases.Where(IsAliasAvailable).ToArray(), getDefaultValue, description)) + : Validation>.Failure(); + + /// + /// Displays an error message for using a non-default custom simulator. + /// + /// The name of the custom simulator. + private static void DisplayCustomSimulatorError(string name) + { + var originalForeground = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.Error.WriteLine($"The simulator '{name}' could not be found."); + Console.ForegroundColor = originalForeground; + Console.Error.WriteLine(); + Console.Error.WriteLine( + $"If '{name}' is a custom simulator, it must be set in the DefaultSimulator project property:"); + Console.Error.WriteLine(); + Console.Error.WriteLine(""); + Console.Error.WriteLine($" {name}"); + Console.Error.WriteLine(""); + } + } +} diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Parsers.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Parsers.cs new file mode 100644 index 00000000000..fa34503db66 --- /dev/null +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Parsers.cs @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace @Namespace +{ + using Microsoft.Quantum.Simulation.Core; + using System; + using System.Collections.Generic; + using System.CommandLine.Parsing; + using System.Linq; + using System.Numerics; + + /// + /// A delegate that parses the value and returns a validation. + /// + /// The type parsed value. + /// The string to parse. + /// The name of the option that the value was used with. + /// A validation of the parsed value. + internal delegate Validation TryParseValue(string value, string optionName = null); + + /// + /// Parsers for command-line arguments. + /// + internal static class Parsers + { + /// + /// Creates an argument parser for a many-valued argument using a parser that operates on each string value. + /// + /// The type of the parsed value. + /// The string parser. + /// The argument parser. + internal static ParseArgument> ParseArgumentsWith(TryParseValue parse) => argument => + { + var optionName = ((OptionResult)argument.Parent).Token.Value; + var validation = argument.Tokens.Select(token => parse(token.Value, optionName)).Sequence(); + if (validation.IsFailure) + { + argument.ErrorMessage = validation.ErrorMessage; + } + return validation.ValueOrDefault; + }; + + /// + /// Creates an argument parser for a single-valued argument using a parser that operates on the string value. + /// + /// The type of the parsed value. + /// The string parser. + /// The argument parser. + internal static ParseArgument ParseArgumentWith(TryParseValue parse) => argument => + { + var values = ParseArgumentsWith(parse)(argument); + return values == null ? default : values.Single(); + }; + + /// + /// Parses a . + /// + /// The string value to parse. + /// The name of the option that the value was used with. + /// A validation of the parsed . + internal static Validation TryParseBigInteger(string value, string optionName = null) => + BigInteger.TryParse(value, out var result) + ? Validation.Success(result) + : Validation.Failure(GetArgumentErrorMessage(value, optionName, typeof(BigInteger))); + + /// + /// Parses a . + /// + /// The string value to parse. + /// The name of the option that the value was used with. + /// A validation of the parsed . + internal static Validation TryParseQRange(string value, string optionName = null) + { + Validation tryParseLong(string longValue) => + long.TryParse(longValue, out var result) + ? Validation.Success(result) + : Validation.Failure(GetArgumentErrorMessage(longValue, optionName, typeof(long))); + + return value.Split("..").Select(tryParseLong).Sequence().Bind(values => + values.Count() == 2 + ? Validation.Success(new QRange(values.ElementAt(0), values.ElementAt(1))) + : values.Count() == 3 + ? Validation.Success(new QRange(values.ElementAt(0), values.ElementAt(1), values.ElementAt(2))) + : Validation.Failure(GetArgumentErrorMessage(value, optionName, typeof(QRange)))); + } + + /// + /// Parses a . + /// + /// The string value to parse. + /// The name of the option that the value was used with. + /// A validation of the parsed . + internal static Validation TryParseQVoid(string value, string optionName = null) => + value.Trim() == QVoid.Instance.ToString() + ? Validation.Success(QVoid.Instance) + : Validation.Failure(GetArgumentErrorMessage(value, optionName, typeof(QVoid))); + + /// + /// Parses a . + /// + /// The string value to parse. + /// The name of the option that the value was used with. + /// A validation of the parsed . + internal static Validation TryParseResult(string value, string optionName = null) => + Enum.TryParse(value, ignoreCase: true, out ResultValue result) + ? Validation.Success(result switch + { + ResultValue.Zero => Result.Zero, + ResultValue.One => Result.One, + var invalid => throw new Exception($"Invalid result value '{invalid}'.") + }) + : Validation.Failure(GetArgumentErrorMessage(value, optionName, typeof(Result))); + + /// + /// Returns an error message string for an argument parser. + /// + /// The value of the argument being parsed. + /// The name of the option. + /// The expected type of the argument. + /// An error message string for an argument parser. + private static string GetArgumentErrorMessage(string arg, string optionName, Type type) => + $"Cannot parse argument '{arg}' for option '{optionName}' as expected type {type}."; + } +} diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Validation.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Validation.cs new file mode 100644 index 00000000000..20b8eaf6571 --- /dev/null +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Validation.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace @Namespace +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Represents either a success or a failure of a process. + /// + /// The type of the success value. + internal struct Validation + { + public bool IsSuccess { get; } + public bool IsFailure { get => !IsSuccess; } + public T Value { get => IsSuccess ? ValueOrDefault : throw new InvalidOperationException(); } + public T ValueOrDefault { get; } + public string ErrorMessage { get; } + + private Validation(bool isSuccess, T value, string errorMessage) + { + IsSuccess = isSuccess; + ValueOrDefault = value; + ErrorMessage = errorMessage; + } + + public static Validation Success(T value) => + new Validation(true, value, default); + + public static Validation Failure(string errorMessage = null) => + new Validation(false, default, errorMessage); + } + + /// + /// Extension methods for . + /// + internal static class ValidationExtensions + { + /// + /// Sequentially composes two validations, passing the value of the first validation to another + /// validation-producing function if the first validation is a success. + /// + /// The type of the first validation's success value. + /// The type of the second validation's success value. + /// The first validation. + /// + /// A function that takes the value of the first validation and returns a second validation. + /// + /// + /// The first validation if the first validation is a failure; otherwise, the return value of calling the bind + /// function on the first validation's success value. + /// + internal static Validation Bind(this Validation validation, Func> bind) => + validation.IsFailure ? Validation.Failure(validation.ErrorMessage) : bind(validation.Value); + + /// + /// Converts an enumerable of validations into a validation of an enumerable. + /// + /// The type of the validation success values. + /// The validations to sequence. + /// + /// A validation that contains an enumerable of the validation values if all of the validations are a success, + /// or the first error message if one of the validations is a failure. + /// + internal static Validation> Sequence(this IEnumerable> validations) => + validations.All(validation => validation.IsSuccess) + ? Validation>.Success(validations.Select(validation => validation.Value)) + : Validation>.Failure(validations.First(validation => validation.IsFailure).ErrorMessage); + + /// + /// Calls the action on the validation value if the validation is a success. + /// + /// The type of the validation's success value. + /// The validation. + /// The action to call if the validation is a success. + internal static void Then(this Validation validation, Action onSuccess) + { + if (validation.IsSuccess) + { + onSuccess(validation.Value); + } + } + } +} diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs deleted file mode 100644 index 672ee3b049f..00000000000 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ /dev/null @@ -1,394 +0,0 @@ -namespace @Namespace -{ - using Microsoft.Quantum.Simulation.Core; - using Microsoft.Quantum.Simulation.Simulators; - using System; - using System.Collections.Generic; - using System.Collections.Immutable; - using System.CommandLine; - using System.CommandLine.Builder; - using System.CommandLine.Help; - using System.CommandLine.Invocation; - using System.CommandLine.Parsing; - using System.Linq; - using System.Numerics; - using System.Threading.Tasks; - - /// - /// The entry point driver is the entry point for the C# application that executes the Q# entry point. - /// - internal static class Driver - { - /// - /// The argument handler for the Q# Unit type. - /// - internal static Argument UnitArgumentHandler - { - get - { - var arg = new Argument(CreateArgumentParser(value => - value.Trim() == QVoid.Instance.ToString() ? (true, QVoid.Instance) : (false, default))); - arg.AddSuggestions(new[] { QVoid.Instance.ToString() }); - return arg; - } - } - - /// - /// The argument handler for the Q# Result type. - /// - internal static Argument ResultArgumentHandler - { - get - { - var arg = new Argument(CreateArgumentParser(value => - Enum.TryParse(value, ignoreCase: true, out ResultValue result) ? result switch - { - ResultValue.Zero => (true, Result.Zero), - ResultValue.One => (true, Result.One), - _ => (false, default) - } : (false, default))); - arg.AddSuggestions(new[] { ResultValue.Zero.ToString(), ResultValue.One.ToString() }); - return arg; - } - } - - /// - /// The argument handler for the Q# BigInt type. - /// - internal static Argument BigIntArgumentHandler => new Argument( - CreateArgumentParser(value => - BigInteger.TryParse(value, out var result) ? (true, result) : (false, default))); - - /// - /// The argument handler for the Q# Range type. - /// - internal static Argument RangeArgumentHandler => new Argument(result => - { - var option = ((OptionResult)result.Parent).Token.Value; - var value = result.Tokens.Single().Value; - var range = ParseRangeFromEnumerable(option, value, value.Split("..")); - if (range.IsFailure) - { - result.ErrorMessage = range.ErrorMessage; - } - return range.ValueOrDefault; - }) - { - Arity = ArgumentArity.ExactlyOne - }; - - /// - /// Runs the entry point. - /// - /// The command-line arguments. - /// The exit code. - private static async Task Main(string[] args) - { - var simulate = new Command("simulate", "(default) Run the program using a local simulator."); - TryCreateOption( - Constants.SimulatorOptions, - () => EntryPoint.DefaultSimulator, - "The name of the simulator to use.").Then(option => - { - option.Argument.AddSuggestions(ImmutableHashSet.Empty - .Add(Constants.QuantumSimulator) - .Add(Constants.ToffoliSimulator) - .Add(Constants.ResourcesEstimator) - .Add(EntryPoint.DefaultSimulator)); - simulate.AddOption(option); - }); - simulate.Handler = CommandHandler.Create(Simulate); - - var root = new RootCommand(EntryPoint.Summary) { simulate }; - foreach (var option in EntryPoint.Options) { root.AddGlobalOption(option); } - - // Set the simulate command as the default. - foreach (var option in simulate.Options) { root.AddOption(option); } - root.Handler = simulate.Handler; - - return await new CommandLineBuilder(root) - .UseDefaults() - .UseHelpBuilder(context => new QsHelpBuilder(context.Console)) - .Build() - .InvokeAsync(args); - } - - /// - /// Simulates the entry point. - /// - /// The entry point. - /// The simulator to use. - /// The exit code. - private static async Task Simulate(EntryPoint entryPoint, string simulator) - { - simulator = DefaultIfShadowed(Constants.SimulatorOptions.First(), simulator, EntryPoint.DefaultSimulator); - switch (simulator) - { - case Constants.ResourcesEstimator: - var resourcesEstimator = new ResourcesEstimator(); - await entryPoint.Run(resourcesEstimator); - Console.WriteLine(resourcesEstimator.ToTSV()); - break; - default: - var (isCustom, createSimulator) = simulator switch - { - Constants.QuantumSimulator => - (false, new Func(() => new QuantumSimulator())), - Constants.ToffoliSimulator => - (false, new Func(() => new ToffoliSimulator())), - _ => (true, EntryPoint.CreateDefaultCustomSimulator) - }; - if (isCustom && simulator != EntryPoint.DefaultSimulator) - { - DisplayCustomSimulatorError(simulator); - return 1; - } - await DisplayEntryPointResult(entryPoint, createSimulator); - break; - } - return 0; - } - - /// - /// Runs the entry point on a simulator and displays its return value. - /// - /// The entry point. - /// A function that creates an instance of the simulator to use. - private static async Task DisplayEntryPointResult( - EntryPoint entryPoint, Func createSimulator) - { - var simulator = createSimulator(); - try - { - var value = await entryPoint.Run(simulator); -#pragma warning disable CS0184 - if (!(value is QVoid)) -#pragma warning restore CS0184 - { - Console.WriteLine(value); - } - } - finally - { - if (simulator is IDisposable disposable) - { - disposable.Dispose(); - } - } - } - - /// - /// Returns true if the alias is available for use by the driver (that is, the alias is not already used by an - /// entry point option). - /// - /// The alias to check. - /// True if the alias is available for use by the driver. - private static bool IsAliasAvailable(string alias) => - !EntryPoint.Options.SelectMany(option => option.RawAliases).Contains(alias); - - /// - /// Returns the default value and displays a warning if the alias is shadowed by an entry point option, and - /// returns the original value otherwise. - /// - /// The type of the values. - /// The primary option alias corresponding to the value. - /// The value of the option given on the command line. - /// The default value for the option. - /// - private static T DefaultIfShadowed(string alias, T value, T defaultValue) - { - if (IsAliasAvailable(alias)) - { - return value; - } - else - { - var originalForeground = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Yellow; - Console.Error.WriteLine($"Warning: Option {alias} is overriden by an entry point parameter name."); - Console.Error.WriteLine($" Using default value {defaultValue}."); - Console.ForegroundColor = originalForeground; - return defaultValue; - } - } - - /// - /// Tries to create an option by removing aliases that are already in use by the entry point. If the first - /// alias, which is considered the primary alias, is in use, then the option is not created. - /// - /// The type of the option's argument. - /// The option's aliases. - /// A function that returns the option's default value. - /// The option's description. - /// The result of trying to create the option. - private static Result> TryCreateOption( - IEnumerable aliases, Func getDefaultValue, string description = null) => - IsAliasAvailable(aliases.First()) - ? Result>.Success( - new Option(aliases.Where(IsAliasAvailable).ToArray(), getDefaultValue, description)) - : Result>.Failure(); - - /// - /// Creates an argument parser that will use a default error message if parsing fails. - /// - /// The type of the argument. - /// - /// A function that takes the argument as a string and returns the parsed value and a boolean to indicate - /// whether parsing succeeded. - /// - /// An argument parser. - private static ParseArgument CreateArgumentParser(Func parse) => result => - { - var (success, value) = parse(result.Tokens.Single().Value); - if (!success) - { - result.ErrorMessage = GetArgumentErrorMessage( - ((OptionResult)result.Parent).Token.Value, result.Tokens.Single().Value, typeof(T)); - } - return value; - }; - - /// - /// Parses a Q# range from an enumerable of strings, where the items are start and end or start, step, and end. - /// - /// The name of the option being parsed. - /// The argument string for the option. - /// The items in the argument. - /// The result of parsing the strings. - private static Result ParseRangeFromEnumerable(string option, string arg, IEnumerable items) => - items.Select(item => TryParseLong(option, item)).Sequence().Bind(values => - values.Count() == 2 - ? Result.Success(new QRange(values.ElementAt(0), values.ElementAt(1))) - : values.Count() == 3 - ? Result.Success(new QRange(values.ElementAt(0), values.ElementAt(1), values.ElementAt(2))) - : Result.Failure(GetArgumentErrorMessage(option, arg, typeof(QRange)))); - - /// - /// Parses a long from a string. - /// - /// The name of the option being parsed. - /// The string to parse. - /// The result of parsing the string. - private static Result TryParseLong(string option, string str) => - long.TryParse(str, out var result) - ? Result.Success(result) - : Result.Failure(GetArgumentErrorMessage(option, str, typeof(long))); - - /// - /// Returns an error message string for an argument parser. - /// - /// The name of the option. - /// The value of the argument being parsed. - /// The expected type of the argument. - /// An error message string for an argument parser. - private static string GetArgumentErrorMessage(string option, string arg, Type type) => - $"Cannot parse argument '{arg}' for option '{option}' as expected type {type}."; - - /// - /// Displays an error message for using a non-default custom simulator. - /// - /// The name of the custom simulator. - private static void DisplayCustomSimulatorError(string name) - { - var originalForeground = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Red; - Console.Error.WriteLine($"The simulator '{name}' could not be found."); - Console.ForegroundColor = originalForeground; - Console.Error.WriteLine(); - Console.Error.WriteLine( - $"If '{name}' is a custom simulator, it must be set in the DefaultSimulator project property:"); - Console.Error.WriteLine(); - Console.Error.WriteLine(""); - Console.Error.WriteLine($" {name}"); - Console.Error.WriteLine(""); - } - } - - /// - /// A modification of the command line class. - /// - internal class QsHelpBuilder : HelpBuilder - { - public QsHelpBuilder(IConsole console) : base(console) { } - - protected override string ArgumentDescriptor(IArgument argument) - { - // Hide long argument descriptors. - var descriptor = base.ArgumentDescriptor(argument); - return descriptor.Length > 30 ? argument.Name : descriptor; - } - } - - /// - /// The result of a process that can either succeed or fail. - /// - /// The type of the result value. - internal struct Result - { - public bool IsSuccess { get; } - public bool IsFailure { get => !IsSuccess; } - public T Value { get => IsSuccess ? ValueOrDefault : throw new InvalidOperationException(); } - public T ValueOrDefault { get; } - public string ErrorMessage { get; } - - private Result(bool isSuccess, T value, string errorMessage) - { - IsSuccess = isSuccess; - ValueOrDefault = value; - ErrorMessage = errorMessage; - } - - public static Result Success(T value) => new Result(true, value, default); - - public static Result Failure(string errorMessage = null) => new Result(false, default, errorMessage); - } - - /// - /// Extension methods for . - /// - internal static class ResultExtensions - { - /// - /// Sequentially composes two results, passing the value of the first result to another result-producing - /// function if the first result is a success. - /// - /// The type of the first result value. - /// The type of the second result value. - /// The first result. - /// A function that takes the value of the first result and returns a second result. - /// - /// The first result if the first result is a failure; otherwise, the result of calling the bind function on the - /// first result's value. - /// - internal static Result Bind(this Result result, Func> bind) => - result.IsFailure ? Result.Failure(result.ErrorMessage) : bind(result.Value); - - /// - /// Converts an enumerable of results into a result of an enumerable. - /// - /// The type of the result values. - /// The results to sequence. - /// - /// A result that contains an enumerable of the result values if all of the results are a success, or the first - /// error message if one of the results is a failure. - /// - internal static Result> Sequence(this IEnumerable> results) => - results.All(result => result.IsSuccess) - ? Result>.Success(results.Select(results => results.Value)) - : Result>.Failure(results.First(results => results.IsFailure).ErrorMessage); - - /// - /// Calls the action on the result value if the result is a success. - /// - /// The type of the result value. - /// The result. - /// The action to call if the result is a success. - internal static void Then(this Result result, Action onSuccess) - { - if (result.IsSuccess) - { - onSuccess(result.Value); - } - } - } -}