From 464a99c64a50f69ad21a005906d604472c2d8ff5 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 22 Apr 2020 21:31:29 -0700 Subject: [PATCH 1/9] Fix array arguments with custom handlers --- src/Simulation/CsharpGeneration/EntryPoint.fs | 39 ++-- .../Microsoft.Quantum.CsharpGeneration.fsproj | 6 +- .../Resources/EntryPoint/Converters.cs | 71 ++++++ .../Driver.cs} | 204 ++---------------- .../Resources/EntryPoint/Result.cs | 48 +++++ 5 files changed, 168 insertions(+), 200 deletions(-) create mode 100644 src/Simulation/CsharpGeneration/Resources/EntryPoint/Converters.cs rename src/Simulation/CsharpGeneration/Resources/{EntryPointDriver.cs => EntryPoint/Driver.cs} (52%) create mode 100644 src/Simulation/CsharpGeneration/Resources/EntryPoint/Result.cs diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 0b1969b169e..2c501648f62 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -66,15 +66,20 @@ let rec private parameters context doc = function | 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) +let private argumentHandler qsType typeName = + let rec suggestions = function + | ArrayType (itemType : ResolvedType) -> suggestions itemType.Resolution + | Result -> ["Zero"; "One"] + | _ -> [] + + match suggestions qsType with + | [] -> None + | suggestions -> + let args = List.concat [ + [``new`` (``type`` (sprintf "System.CommandLine.Argument<%s>" typeName)) ``(`` [] ``)``] + List.map ``literal`` suggestions + ] + ``invoke`` (``ident`` "System.CommandLine.ArgumentExtensions.WithSuggestions") ``(`` args ``)`` |> Some /// A property containing a sequence of command-line options corresponding to each parameter given. let private parameterOptionsProperty parameters = @@ -87,7 +92,7 @@ let private parameterOptionsProperty parameters = then ``literal`` ("-" + name) else ``literal`` "--" <+> ``invoke`` toKebabCaseIdent ``(`` [``literal`` name] ``)`` let members = - argumentHandler qsType.Resolution + argumentHandler qsType.Resolution typeName |> Option.map (fun handler -> ``ident`` "Argument" <-- handler :> ExpressionSyntax) |> Option.toList |> List.append [``ident`` "Required" <-- ``true``] @@ -196,10 +201,16 @@ 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 "Converters.cs", + source "Driver.cs", + source "Result.cs") /// Generates C# source code for a standalone executable that runs the Q# entry point. let internal generate context entryPoint = diff --git a/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj b/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj index 31751fbe1fb..337b56ea9c3 100644 --- a/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj +++ b/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj @@ -8,7 +8,9 @@ - + + + @@ -29,5 +31,5 @@ - + diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Converters.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Converters.cs new file mode 100644 index 00000000000..a033b5bc52e --- /dev/null +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Converters.cs @@ -0,0 +1,71 @@ +namespace @Namespace +{ + using Microsoft.Quantum.Simulation.Core; + using System; + using System.ComponentModel; + using System.Globalization; + using System.Linq; + using System.Numerics; + + /// + /// Converts a string to type . + /// + /// The type of the value to convert from a string. + internal abstract class FromStringConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) => + sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) => + value is string s ? Convert(s) : base.ConvertFrom(context, culture, value); + + protected abstract T Convert(string value); + } + + /// + /// Converts a string to a . + /// + internal class BigIntegerConverter : FromStringConverter + { + protected override BigInteger Convert(string value) => BigInteger.Parse(value); + } + + /// + /// Converts a string to a . + /// + internal class QRangeConverter : FromStringConverter + { + protected override QRange Convert(string value) + { + var values = value.Split("..").Select(long.Parse); + return values.Count() == 2 + ? new QRange(values.ElementAt(0), values.ElementAt(1)) + : values.Count() == 3 + ? new QRange(values.ElementAt(0), values.ElementAt(1), values.ElementAt(2)) + : throw new ArgumentException(); + } + } + + /// + /// Converts a string to . + /// + internal class QVoidConverter : FromStringConverter + { + protected override QVoid Convert(string value) => + value.Trim() == QVoid.Instance.ToString() ? QVoid.Instance : throw new ArgumentException(); + } + + /// + /// Converts a string to a . + /// + internal class ResultConverter : FromStringConverter + { + protected override Result Convert(string value) => + Enum.Parse(value, ignoreCase: true) switch + { + ResultValue.Zero => Result.Zero, + ResultValue.One => Result.One, + _ => throw new ArgumentException() + }; + } +} diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs similarity index 52% rename from src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs rename to src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs index 672ee3b049f..1c31f56db85 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs @@ -10,6 +10,7 @@ using System.CommandLine.Help; using System.CommandLine.Invocation; using System.CommandLine.Parsing; + using System.ComponentModel; using System.Linq; using System.Numerics; using System.Threading.Tasks; @@ -20,63 +21,20 @@ internal static class Driver { /// - /// The argument handler for the Q# Unit type. + /// A modification of the command line class. /// - internal static Argument UnitArgumentHandler + private class QsHelpBuilder : HelpBuilder { - 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; - } - } + public QsHelpBuilder(IConsole console) : base(console) { } - /// - /// The argument handler for the Q# Result type. - /// - internal static Argument ResultArgumentHandler - { - get + protected override string ArgumentDescriptor(IArgument argument) { - 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; + // Hide long argument descriptors. + var descriptor = base.ArgumentDescriptor(argument); + return descriptor.Length > 30 ? argument.Name : descriptor; } } - /// - /// 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. /// @@ -106,6 +64,7 @@ private static async Task Main(string[] args) foreach (var option in simulate.Options) { root.AddOption(option); } root.Handler = simulate.Handler; + RegisterTypeConverters(); return await new CommandLineBuilder(root) .UseDefaults() .UseHelpBuilder(context => new QsHelpBuilder(context.Console)) @@ -113,6 +72,17 @@ private static async Task Main(string[] args) .InvokeAsync(args); } + /// + /// Registers type converters for Q# argument types. + /// + private static void RegisterTypeConverters() + { + TypeDescriptor.AddAttributes(typeof(BigInteger), new TypeConverterAttribute(typeof(BigIntegerConverter))); + TypeDescriptor.AddAttributes(typeof(QRange), new TypeConverterAttribute(typeof(QRangeConverter))); + TypeDescriptor.AddAttributes(typeof(QVoid), new TypeConverterAttribute(typeof(QVoidConverter))); + TypeDescriptor.AddAttributes(typeof(Result), new TypeConverterAttribute(typeof(ResultConverter))); + } + /// /// Simulates the entry point. /// @@ -228,52 +198,6 @@ private static Result> TryCreateOption( 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. /// @@ -303,92 +227,4 @@ private static void DisplayCustomSimulatorError(string 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); - } - } - } } diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Result.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Result.cs new file mode 100644 index 00000000000..0d48aa13072 --- /dev/null +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Result.cs @@ -0,0 +1,48 @@ +namespace @Namespace +{ + using System; + + /// + /// 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 + { + /// + /// 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); + } + } + } +} From 73794f5888ecddcc0d67b8d90e651f634362a1f3 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Thu, 23 Apr 2020 02:45:52 -0700 Subject: [PATCH 2/9] Use single-argument string constructor converters --- src/Simulation/CsharpGeneration/EntryPoint.fs | 71 ++++++++++---- .../Resources/EntryPoint/Converters.cs | 98 +++++++++++++------ .../Resources/EntryPoint/Driver.cs | 14 --- .../Resources/EntryPoint/Result.cs | 16 +++ 4 files changed, 136 insertions(+), 63 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 2c501648f62..179951f6bd7 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -45,24 +45,57 @@ let private constantsClass = constant "ResourcesEstimator" "string" (``literal`` AssemblyConstants.ResourcesEstimator)] ``}`` +/// The name of the C# converter type used for command-line parsing of Q# types. +let rec private converterName = function + | ArrayType (itemType : ResolvedType) -> converterName itemType.Resolution + | BigInt -> Some "BigIntegerConverter" + | Range -> Some "QRangeConverter" + | Result -> Some "ResultConverter" + | UnitType -> Some "QVoidConverter" + | _ -> None + +/// True if the Q# type has a C# converter type. +let rec private typeHasConverter = converterName >> Option.isSome + +/// Adds a validator to the command-line option if the option type uses a custom converter. +let private withConverterValidator qsType (option : ExpressionSyntax) = + match converterName qsType with + | Some converter -> + let parse = ``() =>`` ["value"] (``new`` (``type`` converter) ``(`` [``ident`` "value"] ``)``) + ``invoke`` (``ident`` "ConversionResult.WithValidator") ``(`` [option; upcast parse] ``)`` + | None -> option + +/// The name of the C# type used by the parameter in its command-line option, given its Q# type. +let rec private parameterCsharpType context (qsType : ResolvedType) = + match qsType.Resolution with + | ArrayType itemType -> + sprintf "System.Collections.Generic.IEnumerable<%s>" (parameterCsharpType context itemType) + | _ -> + converterName qsType.Resolution + |> Option.defaultValue (SimulationCode.roslynTypeName context qsType) + +/// Undoes any type conversion that was needed for command-line parsing, so that the argument type is suitable to give +/// to the Q# entry point. +let rec private unconvertArgument context qsType (arg : ExpressionSyntax) = + match qsType with + | ArrayType itemType -> + let arrayTypeName = sprintf "QArray<%s>" (SimulationCode.roslynTypeName context itemType) + let unconverter = ``() =>`` ["value"] (unconvertArgument context itemType.Resolution (``ident`` "value")) + ``new`` (``type`` arrayTypeName) ``(`` + [``invoke`` (``ident`` "System.Linq.Enumerable.Select") ``(`` [arg; upcast unconverter] ``)``] ``)`` + | _ when typeHasConverter qsType -> arg <|.|> ``ident`` "ValueOrDefault" + | _ -> arg + /// 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 = parameterCsharpType 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. @@ -101,6 +134,7 @@ let private parameterOptionsProperty parameters = ``{`` members ``}`` + |> withConverterValidator qsType.Resolution let options = parameters |> Seq.map getOption |> Seq.toList ``property-arrow_get`` optionsEnumerableTypeName "Options" [``public``; ``static``] @@ -121,19 +155,14 @@ 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 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 + let argExpr { Name = name; QsharpType = qsType } = + ``ident`` "this" <|.|> ``ident`` (parameterPropertyName name) + |> unconvertArgument context qsType.Resolution 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" ``<<`` [] ``>>`` diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Converters.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Converters.cs index a033b5bc52e..41cf28976a2 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Converters.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Converters.cs @@ -2,70 +2,112 @@ { using Microsoft.Quantum.Simulation.Core; using System; - using System.ComponentModel; - using System.Globalization; + using System.CommandLine; using System.Linq; using System.Numerics; /// - /// Converts a string to type . + /// The result of trying to convert a string into the type . /// - /// The type of the value to convert from a string. - internal abstract class FromStringConverter : TypeConverter + /// The type of the value. + internal abstract class ConversionResult { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) => - sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); - - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) => - value is string s ? Convert(s) : base.ConvertFrom(context, culture, value); + public bool IsValid { get; protected set; } + public T ValueOrDefault { get; protected set; } + } - protected abstract T Convert(string value); + internal static class ConversionResult + { + /// + /// Adds a validator to the option that requires the argument to be successfully parsed by the given function. + /// + /// The option's type parameter. + /// The type that the parser will produce. + /// The option to add the validator to. + /// The function that must successfully parse the arguments. + /// The option after the validator is added. + internal static Option WithValidator(Option option, Func> parse) + { + option.AddValidator(result => result + .Tokens + .Select(token => token.Value) + .Where(value => !parse(value).IsValid) + .Select(value => + $"Cannot parse argument '{value}' for option '{result.Token.Value}' as expected type {typeof(U)}.") + .FirstOrDefault()); + return option; + } } /// /// Converts a string to a . /// - internal class BigIntegerConverter : FromStringConverter + internal class BigIntegerConverter : ConversionResult { - protected override BigInteger Convert(string value) => BigInteger.Parse(value); + public BigIntegerConverter(string value) + { + IsValid = BigInteger.TryParse(value, out var result); + ValueOrDefault = result; + } } /// /// Converts a string to a . /// - internal class QRangeConverter : FromStringConverter + internal class QRangeConverter : ConversionResult { - protected override QRange Convert(string value) + public QRangeConverter(string value) { - var values = value.Split("..").Select(long.Parse); - return values.Count() == 2 - ? new QRange(values.ElementAt(0), values.ElementAt(1)) - : values.Count() == 3 - ? new QRange(values.ElementAt(0), values.ElementAt(1), values.ElementAt(2)) - : throw new ArgumentException(); + value.Split("..").Select(TryParseLong).Sequence().Then(values => + { + if (values.Count() == 2) + { + IsValid = true; + ValueOrDefault = new QRange(values.ElementAt(0), values.ElementAt(1)); + } + else if (values.Count() == 3) + { + IsValid = true; + ValueOrDefault = new QRange(values.ElementAt(0), values.ElementAt(1), values.ElementAt(2)); + } + }); } + + /// + /// Parses a long from a string. + /// + /// The string to parse. + /// The result of parsing the string. + private static Result TryParseLong(string value) => + long.TryParse(value, out var result) ? Result.Success(result) : Result.Failure(); } /// /// Converts a string to . /// - internal class QVoidConverter : FromStringConverter + internal class QVoidConverter : ConversionResult { - protected override QVoid Convert(string value) => - value.Trim() == QVoid.Instance.ToString() ? QVoid.Instance : throw new ArgumentException(); + public QVoidConverter(string value) + { + IsValid = value.Trim() == QVoid.Instance.ToString(); + ValueOrDefault = QVoid.Instance; + } } /// /// Converts a string to a . /// - internal class ResultConverter : FromStringConverter + internal class ResultConverter : ConversionResult { - protected override Result Convert(string value) => - Enum.Parse(value, ignoreCase: true) switch + public ResultConverter(string value) + { + IsValid = Enum.TryParse(value, ignoreCase: true, out ResultValue result); + ValueOrDefault = result switch { ResultValue.Zero => Result.Zero, ResultValue.One => Result.One, - _ => throw new ArgumentException() + _ => default }; + } } } diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs index 1c31f56db85..00dbea90fe6 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs @@ -10,9 +10,7 @@ using System.CommandLine.Help; using System.CommandLine.Invocation; using System.CommandLine.Parsing; - using System.ComponentModel; using System.Linq; - using System.Numerics; using System.Threading.Tasks; /// @@ -64,7 +62,6 @@ private static async Task Main(string[] args) foreach (var option in simulate.Options) { root.AddOption(option); } root.Handler = simulate.Handler; - RegisterTypeConverters(); return await new CommandLineBuilder(root) .UseDefaults() .UseHelpBuilder(context => new QsHelpBuilder(context.Console)) @@ -72,17 +69,6 @@ private static async Task Main(string[] args) .InvokeAsync(args); } - /// - /// Registers type converters for Q# argument types. - /// - private static void RegisterTypeConverters() - { - TypeDescriptor.AddAttributes(typeof(BigInteger), new TypeConverterAttribute(typeof(BigIntegerConverter))); - TypeDescriptor.AddAttributes(typeof(QRange), new TypeConverterAttribute(typeof(QRangeConverter))); - TypeDescriptor.AddAttributes(typeof(QVoid), new TypeConverterAttribute(typeof(QVoidConverter))); - TypeDescriptor.AddAttributes(typeof(Result), new TypeConverterAttribute(typeof(ResultConverter))); - } - /// /// Simulates the entry point. /// diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Result.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Result.cs index 0d48aa13072..102df699d3e 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Result.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Result.cs @@ -1,6 +1,8 @@ namespace @Namespace { using System; + using System.Collections.Generic; + using System.Linq; /// /// The result of a process that can either succeed or fail. @@ -31,6 +33,20 @@ private Result(bool isSuccess, T value, string errorMessage) /// internal static class ResultExtensions { + /// + /// 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. /// From 0d9251ac5e15826bef7e7a5c950765c5e2324b6f Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Thu, 23 Apr 2020 02:48:08 -0700 Subject: [PATCH 3/9] Seal classes --- .../CsharpGeneration/Resources/EntryPoint/Converters.cs | 8 ++++---- .../CsharpGeneration/Resources/EntryPoint/Driver.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Converters.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Converters.cs index 41cf28976a2..762f2dc8170 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Converters.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Converters.cs @@ -42,7 +42,7 @@ internal static Option WithValidator(Option option, Func /// Converts a string to a . /// - internal class BigIntegerConverter : ConversionResult + internal sealed class BigIntegerConverter : ConversionResult { public BigIntegerConverter(string value) { @@ -54,7 +54,7 @@ public BigIntegerConverter(string value) /// /// Converts a string to a . /// - internal class QRangeConverter : ConversionResult + internal sealed class QRangeConverter : ConversionResult { public QRangeConverter(string value) { @@ -85,7 +85,7 @@ private static Result TryParseLong(string value) => /// /// Converts a string to . /// - internal class QVoidConverter : ConversionResult + internal sealed class QVoidConverter : ConversionResult { public QVoidConverter(string value) { @@ -97,7 +97,7 @@ public QVoidConverter(string value) /// /// Converts a string to a . /// - internal class ResultConverter : ConversionResult + internal sealed class ResultConverter : ConversionResult { public ResultConverter(string value) { diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs index 00dbea90fe6..7b94fa437c9 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs @@ -21,7 +21,7 @@ internal static class Driver /// /// A modification of the command line class. /// - private class QsHelpBuilder : HelpBuilder + private sealed class QsHelpBuilder : HelpBuilder { public QsHelpBuilder(IConsole console) : base(console) { } From 19ffdbd91d97cbc548261cbe6694c8f66a86a6bf Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Thu, 23 Apr 2020 03:03:09 -0700 Subject: [PATCH 4/9] Add tests --- .../Circuits/EntryPointTests.qs | 40 ++++++++++- .../CsharpGeneration.Tests/EntryPointTests.fs | 72 +++++++++++++------ 2 files changed, 90 insertions(+), 22 deletions(-) diff --git a/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs b/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs index 54efe03736d..61344ce5a8a 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,35 @@ 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 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..d9f49a19d93 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -266,31 +266,63 @@ 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 given = test 13 - given ["-u"; "()"] |> yields "" - given ["-u"; "42"] |> fails +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 Range array`` () = + let given = test 15 + 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 16 + 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 17 + given ["--us"; "()"] |> yields "[()]" + given ["--us"; "()"; "()"] |> yields "[(),()]" + given ["--us"; "()"; "()"; "()"] |> yields "[(),(),()]" + given ["--us"; "()"; "unit"; "()"] |> fails // Multiple Options [] let ``Accepts two options`` () = - let given = test 14 + let given = test 18 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 19 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 +330,7 @@ let ``Accepts three options`` () = [] let ``Requires all options`` () = - let given = test 15 + let given = test 19 given ["-b"; "true"; "--xs"; "foo"] |> fails given ["-n"; "7"; "--xs"; "foo"] |> fails given ["-n"; "7"; "-b"; "true"] |> fails @@ -312,13 +344,13 @@ let ``Requires all options`` () = [] let ``Uses kebab-case`` () = - let given = test 16 + let given = test 20 given ["--camel-case-name"; "foo"] |> yields "foo" given ["--camelCaseName"; "foo"] |> fails [] let ``Uses single-dash short names`` () = - let given = test 17 + let given = test 21 given ["-x"; "foo"] |> yields "foo" given ["--x"; "foo"] |> fails @@ -327,7 +359,7 @@ let ``Uses single-dash short names`` () = [] let ``Shadows --simulator`` () = - let given = test 18 + let given = test 22 given ["--simulator"; "foo"] |> yields "foo" given ["--simulator"; AssemblyConstants.ResourcesEstimator] |> yields AssemblyConstants.ResourcesEstimator given ["-s"; AssemblyConstants.ResourcesEstimator; "--simulator"; "foo"] |> fails @@ -335,14 +367,14 @@ let ``Shadows --simulator`` () = [] let ``Shadows -s`` () = - let given = test 19 + let given = test 23 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 24 given ["--version"; "foo"] |> yields "foo" @@ -361,30 +393,30 @@ BorrowedWidth 0" [] let ``Supports QuantumSimulator`` () = - let given = test 21 + let given = test 25 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 25 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 25 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 25 given ["--simulator"; "FooSimulator"; "--use-h"; "false"] |> fails [] let ``Supports default standard simulator`` () = - let given = testWith 21 AssemblyConstants.ResourcesEstimator + let given = testWith 25 AssemblyConstants.ResourcesEstimator given ["--use-h"; "false"] |> yields resourceSummary given ["--simulator"; AssemblyConstants.QuantumSimulator; "--use-h"; "false"] |> yields "Hello, World!" @@ -392,7 +424,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 25 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 +457,7 @@ Options: Commands: simulate (default) Run the program using a local simulator." - let given = test 22 + let given = test 26 given ["--help"] |> yields message given ["-h"] |> yields message given ["-?"] |> yields message From b6ca1ec64184792495f2260d3665f1259a826a8a Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Thu, 23 Apr 2020 15:32:32 -0700 Subject: [PATCH 5/9] Go back to using ParseArgument --- src/Simulation/CsharpGeneration/EntryPoint.fs | 103 ++++++--------- .../Microsoft.Quantum.CsharpGeneration.fsproj | 2 +- .../Resources/EntryPoint/Converters.cs | 113 ---------------- .../Resources/EntryPoint/Driver.cs | 10 -- .../Resources/EntryPoint/Parsers.cs | 122 ++++++++++++++++++ .../Resources/EntryPoint/Result.cs | 15 +++ 6 files changed, 178 insertions(+), 187 deletions(-) delete mode 100644 src/Simulation/CsharpGeneration/Resources/EntryPoint/Converters.cs create mode 100644 src/Simulation/CsharpGeneration/Resources/EntryPoint/Parsers.cs diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 179951f6bd7..d626ed371f1 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -45,46 +45,12 @@ let private constantsClass = constant "ResourcesEstimator" "string" (``literal`` AssemblyConstants.ResourcesEstimator)] ``}`` -/// The name of the C# converter type used for command-line parsing of Q# types. -let rec private converterName = function - | ArrayType (itemType : ResolvedType) -> converterName itemType.Resolution - | BigInt -> Some "BigIntegerConverter" - | Range -> Some "QRangeConverter" - | Result -> Some "ResultConverter" - | UnitType -> Some "QVoidConverter" - | _ -> None - -/// True if the Q# type has a C# converter type. -let rec private typeHasConverter = converterName >> Option.isSome - -/// Adds a validator to the command-line option if the option type uses a custom converter. -let private withConverterValidator qsType (option : ExpressionSyntax) = - match converterName qsType with - | Some converter -> - let parse = ``() =>`` ["value"] (``new`` (``type`` converter) ``(`` [``ident`` "value"] ``)``) - ``invoke`` (``ident`` "ConversionResult.WithValidator") ``(`` [option; upcast parse] ``)`` - | None -> option - /// The name of the C# type used by the parameter in its command-line option, given its Q# type. -let rec private parameterCsharpType context (qsType : ResolvedType) = +let rec private csharpParameterTypeName context (qsType : ResolvedType) = match qsType.Resolution with - | ArrayType itemType -> - sprintf "System.Collections.Generic.IEnumerable<%s>" (parameterCsharpType context itemType) - | _ -> - converterName qsType.Resolution - |> Option.defaultValue (SimulationCode.roslynTypeName context qsType) - -/// Undoes any type conversion that was needed for command-line parsing, so that the argument type is suitable to give -/// to the Q# entry point. -let rec private unconvertArgument context qsType (arg : ExpressionSyntax) = - match qsType with - | ArrayType itemType -> - let arrayTypeName = sprintf "QArray<%s>" (SimulationCode.roslynTypeName context itemType) - let unconverter = ``() =>`` ["value"] (unconvertArgument context itemType.Resolution (``ident`` "value")) - ``new`` (``type`` arrayTypeName) ``(`` - [``invoke`` (``ident`` "System.Linq.Enumerable.Select") ``(`` [arg; upcast unconverter] ``)``] ``)`` - | _ when typeHasConverter qsType -> arg <|.|> ``ident`` "ValueOrDefault" - | _ -> arg + | 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 @@ -93,26 +59,38 @@ let rec private parameters context doc = function | ValidName name -> Seq.singleton { Name = name.Value QsharpType = variable.Type - CsharpTypeName = parameterCsharpType context variable.Type + CsharpTypeName = csharpParameterTypeName context variable.Type Description = ParameterDescription doc name.Value } | 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 qsType typeName = +/// 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 - | [] -> None + | [] -> option | suggestions -> - let args = List.concat [ - [``new`` (``type`` (sprintf "System.CommandLine.Argument<%s>" typeName)) ``(`` [] ``)``] - List.map ``literal`` suggestions - ] - ``invoke`` (``ident`` "System.CommandLine.ArgumentExtensions.WithSuggestions") ``(`` args ``)`` |> Some + 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 = @@ -124,17 +102,16 @@ let private parameterOptionsProperty parameters = if name.Length = 1 then ``literal`` ("-" + name) else ``literal`` "--" <+> ``invoke`` toKebabCaseIdent ``(`` [``literal`` name] ``)`` - let members = - argumentHandler qsType.Resolution typeName - |> 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``] ``}`` - |> withConverterValidator qsType.Resolution + |> withSuggestions qsType.Resolution let options = parameters |> Seq.map getOption |> Seq.toList ``property-arrow_get`` optionsEnumerableTypeName "Options" [``public``; ``static``] @@ -156,8 +133,12 @@ let private runMethod context (entryPoint : QsCallable) = let factoryParamName = "__factory__" let argExpr { Name = name; QsharpType = qsType } = - ``ident`` "this" <|.|> ``ident`` (parameterPropertyName name) - |> unconvertArgument context qsType.Resolution + let property = ``ident`` "this" <|.|> ``ident`` (parameterPropertyName name) + match qsType.Resolution with + | ArrayType itemType -> + let arrayTypeName = sprintf "QArray<%s>" (SimulationCode.roslynTypeName context itemType) + ``new`` (``type`` arrayTypeName) ``(`` [property] ``)`` + | _ -> property let callArgs : seq = Seq.concat [ @@ -235,11 +216,7 @@ let private driver (entryPoint : QsCallable) = 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 "Converters.cs", - source "Driver.cs", - source "Result.cs") + String.Join (Environment.NewLine, source "Driver.cs", source "Parsers.cs", source "Result.cs") /// Generates C# source code for a standalone executable that runs the Q# entry point. let internal generate context entryPoint = diff --git a/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj b/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj index 337b56ea9c3..c18a42bb118 100644 --- a/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj +++ b/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj @@ -8,7 +8,7 @@ - + diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Converters.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Converters.cs deleted file mode 100644 index 762f2dc8170..00000000000 --- a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Converters.cs +++ /dev/null @@ -1,113 +0,0 @@ -namespace @Namespace -{ - using Microsoft.Quantum.Simulation.Core; - using System; - using System.CommandLine; - using System.Linq; - using System.Numerics; - - /// - /// The result of trying to convert a string into the type . - /// - /// The type of the value. - internal abstract class ConversionResult - { - public bool IsValid { get; protected set; } - public T ValueOrDefault { get; protected set; } - } - - internal static class ConversionResult - { - /// - /// Adds a validator to the option that requires the argument to be successfully parsed by the given function. - /// - /// The option's type parameter. - /// The type that the parser will produce. - /// The option to add the validator to. - /// The function that must successfully parse the arguments. - /// The option after the validator is added. - internal static Option WithValidator(Option option, Func> parse) - { - option.AddValidator(result => result - .Tokens - .Select(token => token.Value) - .Where(value => !parse(value).IsValid) - .Select(value => - $"Cannot parse argument '{value}' for option '{result.Token.Value}' as expected type {typeof(U)}.") - .FirstOrDefault()); - return option; - } - } - - /// - /// Converts a string to a . - /// - internal sealed class BigIntegerConverter : ConversionResult - { - public BigIntegerConverter(string value) - { - IsValid = BigInteger.TryParse(value, out var result); - ValueOrDefault = result; - } - } - - /// - /// Converts a string to a . - /// - internal sealed class QRangeConverter : ConversionResult - { - public QRangeConverter(string value) - { - value.Split("..").Select(TryParseLong).Sequence().Then(values => - { - if (values.Count() == 2) - { - IsValid = true; - ValueOrDefault = new QRange(values.ElementAt(0), values.ElementAt(1)); - } - else if (values.Count() == 3) - { - IsValid = true; - ValueOrDefault = new QRange(values.ElementAt(0), values.ElementAt(1), values.ElementAt(2)); - } - }); - } - - /// - /// Parses a long from a string. - /// - /// The string to parse. - /// The result of parsing the string. - private static Result TryParseLong(string value) => - long.TryParse(value, out var result) ? Result.Success(result) : Result.Failure(); - } - - /// - /// Converts a string to . - /// - internal sealed class QVoidConverter : ConversionResult - { - public QVoidConverter(string value) - { - IsValid = value.Trim() == QVoid.Instance.ToString(); - ValueOrDefault = QVoid.Instance; - } - } - - /// - /// Converts a string to a . - /// - internal sealed class ResultConverter : ConversionResult - { - public ResultConverter(string value) - { - IsValid = Enum.TryParse(value, ignoreCase: true, out ResultValue result); - ValueOrDefault = result switch - { - ResultValue.Zero => Result.Zero, - ResultValue.One => Result.One, - _ => default - }; - } - } -} diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs index 7b94fa437c9..63cf82296a0 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs @@ -184,16 +184,6 @@ private static Result> TryCreateOption( new Option(aliases.Where(IsAliasAvailable).ToArray(), getDefaultValue, description)) : Result>.Failure(); - /// - /// 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. /// diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Parsers.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Parsers.cs new file mode 100644 index 00000000000..d8bbc8b0fb6 --- /dev/null +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Parsers.cs @@ -0,0 +1,122 @@ +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 the result. + /// + /// The type parsed value. + /// The string to parse. + /// The name of the option that the value was used with. + /// The result of parsing the value. + internal delegate Result 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 result = argument.Tokens.Select(token => parse(token.Value, optionName)).Sequence(); + if (result.IsFailure) + { + argument.ErrorMessage = result.ErrorMessage; + } + return result.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. + /// The parsed result. + internal static Result TryParseBigInteger(string value, string optionName = null) => + BigInteger.TryParse(value, out var result) + ? Result.Success(result) + : Result.Failure(GetArgumentErrorMessage(value, optionName, typeof(BigInteger))); + + /// + /// Parses a . + /// + /// The string value to parse. + /// The name of the option that the value was used with. + /// The parsed result. + internal static Result TryParseQRange(string value, string optionName = null) + { + Result tryParseLong(string longValue) => + long.TryParse(longValue, out var result) + ? Result.Success(result) + : Result.Failure(GetArgumentErrorMessage(longValue, optionName, typeof(long))); + + return value.Split("..").Select(tryParseLong).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(value, optionName, typeof(QRange)))); + } + + /// + /// Parses a . + /// + /// The string value to parse. + /// The name of the option that the value was used with. + /// The parsed result. + internal static Result TryParseQVoid(string value, string optionName = null) => + value.Trim() == QVoid.Instance.ToString() + ? Result.Success(QVoid.Instance) + : Result.Failure(GetArgumentErrorMessage(value, optionName, typeof(QVoid))); + + /// + /// Parses a . + /// + /// The string value to parse. + /// The name of the option that the value was used with. + /// The parsed result. + internal static Result TryParseResult(string value, string optionName = null) => + Enum.TryParse(value, ignoreCase: true, out ResultValue result) + ? Result.Success(result switch + { + ResultValue.Zero => Result.Zero, + ResultValue.One => Result.One, + var invalid => throw new Exception($"Invalid result value '{invalid}'.") + }) + : Result.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/Result.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Result.cs index 102df699d3e..e0c81fb4f74 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Result.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Result.cs @@ -33,6 +33,21 @@ private Result(bool isSuccess, T value, string errorMessage) /// 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. /// From 321a55ed84fbe94269cbe83e4df49f3f583215b1 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Thu, 23 Apr 2020 15:52:54 -0700 Subject: [PATCH 6/9] Rename Result to Validation --- src/Simulation/CsharpGeneration/EntryPoint.fs | 2 +- .../Microsoft.Quantum.CsharpGeneration.fsproj | 4 +- .../Resources/EntryPoint/Driver.cs | 8 +- .../Resources/EntryPoint/Parsers.cs | 54 ++++++------ .../Resources/EntryPoint/Result.cs | 79 ------------------ .../Resources/EntryPoint/Validation.cs | 83 +++++++++++++++++++ 6 files changed, 117 insertions(+), 113 deletions(-) delete mode 100644 src/Simulation/CsharpGeneration/Resources/EntryPoint/Result.cs create mode 100644 src/Simulation/CsharpGeneration/Resources/EntryPoint/Validation.cs diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index d626ed371f1..480eb52f63b 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -216,7 +216,7 @@ let private driver (entryPoint : QsCallable) = 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 "Result.cs") + 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 internal generate context entryPoint = diff --git a/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj b/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj index c18a42bb118..b8bc5471a8b 100644 --- a/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj +++ b/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj @@ -8,9 +8,9 @@ - - + + diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs index 63cf82296a0..8cd6d3ebd10 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs @@ -176,13 +176,13 @@ private static T DefaultIfShadowed(string alias, T value, T defaultValue) /// 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( + /// A validation of the option. + private static Validation> TryCreateOption( IEnumerable aliases, Func getDefaultValue, string description = null) => IsAliasAvailable(aliases.First()) - ? Result>.Success( + ? Validation>.Success( new Option(aliases.Where(IsAliasAvailable).ToArray(), getDefaultValue, description)) - : Result>.Failure(); + : Validation>.Failure(); /// /// Displays an error message for using a non-default custom simulator. diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Parsers.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Parsers.cs index d8bbc8b0fb6..76792b812ef 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Parsers.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Parsers.cs @@ -8,13 +8,13 @@ using System.Numerics; /// - /// A delegate that parses the value and returns the result. + /// 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. - /// The result of parsing the value. - internal delegate Result TryParseValue(string value, string optionName = null); + /// A validation of the parsed value. + internal delegate Validation TryParseValue(string value, string optionName = null); /// /// Parsers for command-line arguments. @@ -30,12 +30,12 @@ internal static class Parsers internal static ParseArgument> ParseArgumentsWith(TryParseValue parse) => argument => { var optionName = ((OptionResult)argument.Parent).Token.Value; - var result = argument.Tokens.Select(token => parse(token.Value, optionName)).Sequence(); - if (result.IsFailure) + var validation = argument.Tokens.Select(token => parse(token.Value, optionName)).Sequence(); + if (validation.IsFailure) { - argument.ErrorMessage = result.ErrorMessage; + argument.ErrorMessage = validation.ErrorMessage; } - return result.ValueOrDefault; + return validation.ValueOrDefault; }; /// @@ -55,31 +55,31 @@ internal static ParseArgument ParseArgumentWith(TryParseValue parse) => /// /// The string value to parse. /// The name of the option that the value was used with. - /// The parsed result. - internal static Result TryParseBigInteger(string value, string optionName = null) => + /// A validation of the parsed . + internal static Validation TryParseBigInteger(string value, string optionName = null) => BigInteger.TryParse(value, out var result) - ? Result.Success(result) - : Result.Failure(GetArgumentErrorMessage(value, optionName, typeof(BigInteger))); + ? 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. - /// The parsed result. - internal static Result TryParseQRange(string value, string optionName = null) + /// A validation of the parsed . + internal static Validation TryParseQRange(string value, string optionName = null) { - Result tryParseLong(string longValue) => + Validation tryParseLong(string longValue) => long.TryParse(longValue, out var result) - ? Result.Success(result) - : Result.Failure(GetArgumentErrorMessage(longValue, optionName, typeof(long))); + ? Validation.Success(result) + : Validation.Failure(GetArgumentErrorMessage(longValue, optionName, typeof(long))); return value.Split("..").Select(tryParseLong).Sequence().Bind(values => values.Count() == 2 - ? Result.Success(new QRange(values.ElementAt(0), values.ElementAt(1))) + ? Validation.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(value, optionName, typeof(QRange)))); + ? Validation.Success(new QRange(values.ElementAt(0), values.ElementAt(1), values.ElementAt(2))) + : Validation.Failure(GetArgumentErrorMessage(value, optionName, typeof(QRange)))); } /// @@ -87,27 +87,27 @@ Result tryParseLong(string longValue) => /// /// The string value to parse. /// The name of the option that the value was used with. - /// The parsed result. - internal static Result TryParseQVoid(string value, string optionName = null) => + /// A validation of the parsed . + internal static Validation TryParseQVoid(string value, string optionName = null) => value.Trim() == QVoid.Instance.ToString() - ? Result.Success(QVoid.Instance) - : Result.Failure(GetArgumentErrorMessage(value, optionName, typeof(QVoid))); + ? 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. - /// The parsed result. - internal static Result TryParseResult(string value, string optionName = null) => + /// A validation of the parsed . + internal static Validation TryParseResult(string value, string optionName = null) => Enum.TryParse(value, ignoreCase: true, out ResultValue result) - ? Result.Success(result switch + ? Validation.Success(result switch { ResultValue.Zero => Result.Zero, ResultValue.One => Result.One, var invalid => throw new Exception($"Invalid result value '{invalid}'.") }) - : Result.Failure(GetArgumentErrorMessage(value, optionName, typeof(Result))); + : Validation.Failure(GetArgumentErrorMessage(value, optionName, typeof(Result))); /// /// Returns an error message string for an argument parser. diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Result.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Result.cs deleted file mode 100644 index e0c81fb4f74..00000000000 --- a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Result.cs +++ /dev/null @@ -1,79 +0,0 @@ -namespace @Namespace -{ - using System; - using System.Collections.Generic; - using System.Linq; - - /// - /// 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); - } - } - } -} diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Validation.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Validation.cs new file mode 100644 index 00000000000..6269e4031c7 --- /dev/null +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Validation.cs @@ -0,0 +1,83 @@ +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); + } + } + } +} From 38bf601034ced03b1adaaad016d52b3b9e95f2fa Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Fri, 24 Apr 2020 10:16:41 -0700 Subject: [PATCH 7/9] Add tests for Pauli arrays --- .../Circuits/EntryPointTests.qs | 9 ++++ .../CsharpGeneration.Tests/EntryPointTests.fs | 48 ++++++++++++------- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs b/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs index 61344ce5a8a..4f12bc51f3a 100644 --- a/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs +++ b/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs @@ -133,6 +133,15 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- +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[] { diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index d9f49a19d93..3d094463b5c 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 [] @@ -288,8 +292,16 @@ let ``Accepts BigInt array`` () = given ["--bs"; "1"; "2"; "4.2"] |> fails [] -let ``Accepts Range array`` () = +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]" @@ -298,14 +310,14 @@ let ``Accepts Range array`` () = [] let ``Accepts Result array`` () = - let given = test 16 + 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 17 + let given = test 18 given ["--us"; "()"] |> yields "[()]" given ["--us"; "()"; "()"] |> yields "[(),()]" given ["--us"; "()"; "()"; "()"] |> yields "[(),(),()]" @@ -316,13 +328,13 @@ let ``Accepts Unit array`` () = [] let ``Accepts two options`` () = - let given = test 18 + 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 19 + 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]" @@ -330,7 +342,7 @@ let ``Accepts three options`` () = [] let ``Requires all options`` () = - let given = test 19 + let given = test 20 given ["-b"; "true"; "--xs"; "foo"] |> fails given ["-n"; "7"; "--xs"; "foo"] |> fails given ["-n"; "7"; "-b"; "true"] |> fails @@ -344,13 +356,13 @@ let ``Requires all options`` () = [] let ``Uses kebab-case`` () = - let given = test 20 + let given = test 21 given ["--camel-case-name"; "foo"] |> yields "foo" given ["--camelCaseName"; "foo"] |> fails [] let ``Uses single-dash short names`` () = - let given = test 21 + let given = test 22 given ["-x"; "foo"] |> yields "foo" given ["--x"; "foo"] |> fails @@ -359,7 +371,7 @@ let ``Uses single-dash short names`` () = [] let ``Shadows --simulator`` () = - let given = test 22 + let given = test 23 given ["--simulator"; "foo"] |> yields "foo" given ["--simulator"; AssemblyConstants.ResourcesEstimator] |> yields AssemblyConstants.ResourcesEstimator given ["-s"; AssemblyConstants.ResourcesEstimator; "--simulator"; "foo"] |> fails @@ -367,14 +379,14 @@ let ``Shadows --simulator`` () = [] let ``Shadows -s`` () = - let given = test 23 + 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 24 + let given = test 25 given ["--version"; "foo"] |> yields "foo" @@ -393,30 +405,30 @@ BorrowedWidth 0" [] let ``Supports QuantumSimulator`` () = - let given = test 25 + 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 25 + 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 25 + 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 25 + let given = test 26 given ["--simulator"; "FooSimulator"; "--use-h"; "false"] |> fails [] let ``Supports default standard simulator`` () = - let given = testWith 25 AssemblyConstants.ResourcesEstimator + let given = testWith 26 AssemblyConstants.ResourcesEstimator given ["--use-h"; "false"] |> yields resourceSummary given ["--simulator"; AssemblyConstants.QuantumSimulator; "--use-h"; "false"] |> yields "Hello, World!" @@ -424,7 +436,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 25 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!" @@ -457,7 +469,7 @@ Options: Commands: simulate (default) Run the program using a local simulator." - let given = test 26 + let given = test 27 given ["--help"] |> yields message given ["-h"] |> yields message given ["-?"] |> yields message From 6a55f00622ff084a8ff0fb9796d64bb43906ad1f Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Fri, 24 Apr 2020 10:21:25 -0700 Subject: [PATCH 8/9] Add copyright notices --- src/Simulation/CsharpGeneration/EntryPoint.fs | 5 ++++- .../CsharpGeneration/Resources/EntryPoint/Driver.cs | 5 ++++- .../CsharpGeneration/Resources/EntryPoint/Parsers.cs | 5 ++++- .../CsharpGeneration/Resources/EntryPoint/Validation.cs | 5 ++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 480eb52f63b..e1a8220562d 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -1,4 +1,7 @@ -module internal Microsoft.Quantum.QsCompiler.CsharpGeneration.EntryPoint +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +module internal Microsoft.Quantum.QsCompiler.CsharpGeneration.EntryPoint open Microsoft.CodeAnalysis.CSharp open Microsoft.CodeAnalysis.CSharp.Syntax diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs index 8cd6d3ebd10..d461e0f133e 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs @@ -1,4 +1,7 @@ -namespace @Namespace +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace @Namespace { using Microsoft.Quantum.Simulation.Core; using Microsoft.Quantum.Simulation.Simulators; diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Parsers.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Parsers.cs index 76792b812ef..fa34503db66 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Parsers.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Parsers.cs @@ -1,4 +1,7 @@ -namespace @Namespace +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace @Namespace { using Microsoft.Quantum.Simulation.Core; using System; diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Validation.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Validation.cs index 6269e4031c7..20b8eaf6571 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Validation.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Validation.cs @@ -1,4 +1,7 @@ -namespace @Namespace +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace @Namespace { using System; using System.Collections.Generic; From d2150729c1f35dabd8153268828d35539b7522a0 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Fri, 24 Apr 2020 10:36:46 -0700 Subject: [PATCH 9/9] Add test for repeating array option name --- src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index 3d094463b5c..0e93aeb32e5 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -323,6 +323,14 @@ let ``Accepts Unit array`` () = given ["--us"; "()"; "()"; "()"] |> yields "[(),(),()]" given ["--us"; "()"; "unit"; "()"] |> fails +[] +let ``Supports repeat-name array syntax`` () = + let given = test 13 + 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