From 501ad892f2c9685991ceae95590567702b8a0ee1 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 6 Apr 2020 17:26:28 -0700 Subject: [PATCH 01/70] Start set up for entry point code generation --- src/Simulation/CsharpGeneration/Context.fs | 17 ++++++---- src/Simulation/CsharpGeneration/EntryPoint.fs | 33 +++++++++++++++++++ .../Microsoft.Quantum.CsharpGeneration.fsproj | 1 + .../CsharpGeneration/RewriteStep.fs | 17 ++++++++-- 4 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 src/Simulation/CsharpGeneration/EntryPoint.fs diff --git a/src/Simulation/CsharpGeneration/Context.fs b/src/Simulation/CsharpGeneration/Context.fs index b9f93661907..f3981562479 100644 --- a/src/Simulation/CsharpGeneration/Context.fs +++ b/src/Simulation/CsharpGeneration/Context.fs @@ -65,6 +65,7 @@ type CodegenContext = { current : QsQualifiedName option signature : ResolvedSignature option fileName : string option + entryPoints : IEnumerable } with static member public Create (syntaxTree, assemblyConstants) = @@ -82,15 +83,17 @@ type CodegenContext = { result.ToImmutableDictionary() { - assemblyConstants = assemblyConstants; - allQsElements = syntaxTree; - byName = callablesByName; - allUdts = udts; - allCallables = callables; + assemblyConstants = assemblyConstants + allQsElements = syntaxTree + byName = callablesByName + allUdts = udts + allCallables = callables declarationPositions = positionInfos.ToImmutableDictionary((fun g -> g.Key), (fun g -> g.ToImmutableSortedSet())) - current = None; - fileName = None; + current = None + fileName = None signature = None + // TODO: Find the entry points automatically. + entryPoints = [ {Namespace = NonNullable<_>.New "QsSandbox"; Name = NonNullable<_>.New "HelloQ"} ] } static member public Create syntaxTree = diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs new file mode 100644 index 00000000000..aa4025c0ea5 --- /dev/null +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -0,0 +1,33 @@ +module Microsoft.Quantum.QsCompiler.CsharpGeneration.EntryPoint + +open Microsoft.CodeAnalysis.CSharp.Syntax +open Microsoft.Quantum.QsCompiler.SyntaxTree +open Microsoft.Quantum.RoslynWrapper + + +/// The name of the entry point runner class. +let private entryPointRunnerClassName = "__QsEntryPointRunner__" + +/// Returns a C# class that can run the entry point using command-line options to provide the entry point's arguments. +let private getEntryPointRunner = + ``class`` entryPointRunnerClassName ``<<`` [] ``>>`` + ``:`` None ``,`` [] + [``internal``] + ``{`` + // TODO + [] + ``}`` + +/// Generates the C# source code for a standalone executable that runs the Q# entry point. +let internal generate (entryPoint : QsQualifiedName) = + let ns = + ``#line hidden`` <| + ``namespace`` entryPoint.Namespace.Value + ``{`` + [] + [getEntryPointRunner] + ``}`` + :> MemberDeclarationSyntax + + ``compilation unit`` [] [] [ns] + |> SimulationCode.formatSyntaxTree diff --git a/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj b/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj index 316ad43e8cc..97619bfec4c 100644 --- a/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj +++ b/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj @@ -12,6 +12,7 @@ + diff --git a/src/Simulation/CsharpGeneration/RewriteStep.fs b/src/Simulation/CsharpGeneration/RewriteStep.fs index e8485c64efc..93b770850a3 100644 --- a/src/Simulation/CsharpGeneration/RewriteStep.fs +++ b/src/Simulation/CsharpGeneration/RewriteStep.fs @@ -44,7 +44,20 @@ type Emitter() = for source in allSources do let content = SimulationCode.generate source context CompilationLoader.GeneratedFile(source, dir, ".g.cs", content) |> ignore - transformed <- compilation - true + // TODO: There is not really any reason why this needs be tied to a particular source file, except + // GeneratedFile requires one. + // TODO: Show diagnostic if there is more than one entry point. + // TODO: Remove filter once entry points are automatically populated. + let validEntryPoints = + context.entryPoints + |> Seq.filter (fun entryPoint -> context.allCallables.ContainsKey entryPoint) + match Seq.tryExactlyOne validEntryPoints with + | Some entryPoint -> + let source = context.allCallables.[entryPoint].SourceFile + let content = EntryPoint.generate entryPoint + CompilationLoader.GeneratedFile(source, dir, ".EntryPoint.g.cs", content) |> ignore + | None -> () + transformed <- compilation + true From d5787a22938e5a6f2ba427b3110e2b165e19c44c Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 7 Apr 2020 16:32:49 -0700 Subject: [PATCH 02/70] Generate sequence of command-line options --- src/Simulation/CsharpGeneration/EntryPoint.fs | 49 ++++++++++++++----- .../CsharpGeneration/RewriteStep.fs | 6 +-- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index aa4025c0ea5..9f4ce34016b 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -1,33 +1,60 @@ module Microsoft.Quantum.QsCompiler.CsharpGeneration.EntryPoint open Microsoft.CodeAnalysis.CSharp.Syntax +open Microsoft.Quantum.QsCompiler.SyntaxTokens open Microsoft.Quantum.QsCompiler.SyntaxTree open Microsoft.Quantum.RoslynWrapper -/// The name of the entry point runner class. -let private entryPointRunnerClassName = "__QsEntryPointRunner__" +type private ArgumentTuple = QsTuple> + +/// Returns a sequence of all of the named items in the argument tuple and their respective C# types. +let rec private getArguments context = function + | QsTupleItem variable -> + match variable.VariableName with + | ValidName name -> Seq.singleton (name.Value, SimulationCode.roslynTypeName context variable.Type) + | InvalidName -> Seq.empty + | QsTuple items -> items |> Seq.map (getArguments context) |> Seq.concat + +/// Returns a property containing a sequence of command-line options corresponding to each named argument in the entry +/// point. +let private getArgumentOptionsProperty context (arguments : ArgumentTuple) = + let optionTypeName = "System.CommandLine.Option" + let optionsEnumerableTypeName = sprintf "System.Collections.Generic.IEnumerable<%s>" optionTypeName + let getOption (name, typeName) = + // TODO: Generate diagnostic if argument option name conflicts with a standard option name. + // TODO: Use kebab-case. + // TODO: We might need to convert IQArray to a standard array type. + let optionName = "--" + name + ``new init`` (``type`` [sprintf "%s<%s>" optionTypeName typeName]) ``(`` [``literal`` optionName] ``)`` + ``{`` + [``ident`` "Required" <-- ``true``] + ``}`` + let options = arguments |> getArguments context |> Seq.map getOption |> Seq.toList + ``property-arrow_get`` optionsEnumerableTypeName "Options" [``public``; ``static``] + ``get`` (``=>`` (``new array`` (Some optionTypeName) options)) /// Returns a C# class that can run the entry point using command-line options to provide the entry point's arguments. -let private getEntryPointRunner = - ``class`` entryPointRunnerClassName ``<<`` [] ``>>`` +let private getEntryPointRunner context entryPoint = + ``class`` "__QsEntryPointRunner__" ``<<`` [] ``>>`` ``:`` None ``,`` [] [``internal``] ``{`` - // TODO - [] + [getArgumentOptionsProperty context entryPoint.ArgumentTuple] ``}`` /// Generates the C# source code for a standalone executable that runs the Q# entry point. -let internal generate (entryPoint : QsQualifiedName) = +let internal generate context (entryPoint : QsCallable) = let ns = - ``#line hidden`` <| - ``namespace`` entryPoint.Namespace.Value + ``namespace`` entryPoint.FullName.Namespace.Value ``{`` [] - [getEntryPointRunner] + [getEntryPointRunner context entryPoint] ``}`` :> MemberDeclarationSyntax - ``compilation unit`` [] [] [ns] + ``compilation unit`` + [] + (Seq.map ``using`` SimulationCode.autoNamespaces) + [ns] |> SimulationCode.formatSyntaxTree diff --git a/src/Simulation/CsharpGeneration/RewriteStep.fs b/src/Simulation/CsharpGeneration/RewriteStep.fs index 93b770850a3..b5414d54da7 100644 --- a/src/Simulation/CsharpGeneration/RewriteStep.fs +++ b/src/Simulation/CsharpGeneration/RewriteStep.fs @@ -54,9 +54,9 @@ type Emitter() = |> Seq.filter (fun entryPoint -> context.allCallables.ContainsKey entryPoint) match Seq.tryExactlyOne validEntryPoints with | Some entryPoint -> - let source = context.allCallables.[entryPoint].SourceFile - let content = EntryPoint.generate entryPoint - CompilationLoader.GeneratedFile(source, dir, ".EntryPoint.g.cs", content) |> ignore + let callable = context.allCallables.[entryPoint] + let content = EntryPoint.generate context callable + CompilationLoader.GeneratedFile(callable.SourceFile, dir, ".EntryPoint.g.cs", content) |> ignore | None -> () transformed <- compilation From bb1b8a213a93707e743ab108e43b1f8071714ca9 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 7 Apr 2020 17:34:06 -0700 Subject: [PATCH 03/70] Generate argument properties --- src/Simulation/CsharpGeneration/EntryPoint.fs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 9f4ce34016b..6799b9ec8ee 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -6,8 +6,6 @@ open Microsoft.Quantum.QsCompiler.SyntaxTree open Microsoft.Quantum.RoslynWrapper -type private ArgumentTuple = QsTuple> - /// Returns a sequence of all of the named items in the argument tuple and their respective C# types. let rec private getArguments context = function | QsTupleItem variable -> @@ -16,9 +14,8 @@ let rec private getArguments context = function | InvalidName -> Seq.empty | QsTuple items -> items |> Seq.map (getArguments context) |> Seq.concat -/// Returns a property containing a sequence of command-line options corresponding to each named argument in the entry -/// point. -let private getArgumentOptionsProperty context (arguments : ArgumentTuple) = +/// Returns a property containing a sequence of command-line options corresponding to each argument given. +let private getArgumentOptionsProperty arguments = let optionTypeName = "System.CommandLine.Option" let optionsEnumerableTypeName = sprintf "System.Collections.Generic.IEnumerable<%s>" optionTypeName let getOption (name, typeName) = @@ -30,17 +27,29 @@ let private getArgumentOptionsProperty context (arguments : ArgumentTuple) = ``{`` [``ident`` "Required" <-- ``true``] ``}`` - let options = arguments |> getArguments context |> Seq.map getOption |> Seq.toList + let options = arguments |> Seq.map getOption |> Seq.toList ``property-arrow_get`` optionsEnumerableTypeName "Options" [``public``; ``static``] ``get`` (``=>`` (``new array`` (Some optionTypeName) options)) +/// Returns a sequence of properties corresponding to each argument given. +let private getArgumentProperties = Seq.map <| function + // TODO: Use PascalCase. + (name, typeName) -> ``prop`` typeName name [``public``] + /// Returns a C# class that can run the entry point using command-line options to provide the entry point's arguments. let private getEntryPointRunner context entryPoint = + let arguments = getArguments context entryPoint.ArgumentTuple + let members : seq = + Seq.concat [ + Seq.singleton (upcast getArgumentOptionsProperty arguments) + getArgumentProperties arguments |> Seq.map (fun property -> upcast property) + ] + ``class`` "__QsEntryPointRunner__" ``<<`` [] ``>>`` ``:`` None ``,`` [] [``internal``] ``{`` - [getArgumentOptionsProperty context entryPoint.ArgumentTuple] + members ``}`` /// Generates the C# source code for a standalone executable that runs the Q# entry point. @@ -51,7 +60,6 @@ let internal generate context (entryPoint : QsCallable) = [] [getEntryPointRunner context entryPoint] ``}`` - :> MemberDeclarationSyntax ``compilation unit`` [] From b2de6e5f22b38c462a59b45418760098a24badda Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 7 Apr 2020 18:38:13 -0700 Subject: [PATCH 04/70] Capitalize first letter of argument property --- src/Simulation/CsharpGeneration/EntryPoint.fs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 6799b9ec8ee..836bc03d802 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -32,9 +32,9 @@ let private getArgumentOptionsProperty arguments = ``get`` (``=>`` (``new array`` (Some optionTypeName) options)) /// Returns a sequence of properties corresponding to each argument given. -let private getArgumentProperties = Seq.map <| function - // TODO: Use PascalCase. - (name, typeName) -> ``prop`` typeName name [``public``] +let private getArgumentProperties = + let capitalize (s : string) = s.Substring(0, 1).ToUpper() + s.Substring 1 + Seq.map (fun (name, typeName) -> ``prop`` typeName (capitalize name) [``public``]) /// Returns a C# class that can run the entry point using command-line options to provide the entry point's arguments. let private getEntryPointRunner context entryPoint = From c345fa418b1c68b1fb0a96d3620fef67b7b2c395 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 7 Apr 2020 19:02:01 -0700 Subject: [PATCH 05/70] Add autogen comment to entry point file --- src/Simulation/CsharpGeneration/EntryPoint.fs | 1 + .../CsharpGeneration/SimulationCode.fs | 24 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 836bc03d802..79ba66fd5dc 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -65,4 +65,5 @@ let internal generate context (entryPoint : QsCallable) = [] (Seq.map ``using`` SimulationCode.autoNamespaces) [ns] + |> ``with leading comments`` SimulationCode.autogenComment |> SimulationCode.formatSyntaxTree diff --git a/src/Simulation/CsharpGeneration/SimulationCode.fs b/src/Simulation/CsharpGeneration/SimulationCode.fs index 1c9fcbca2f3..bd6abf5fe8d 100644 --- a/src/Simulation/CsharpGeneration/SimulationCode.fs +++ b/src/Simulation/CsharpGeneration/SimulationCode.fs @@ -1564,6 +1564,17 @@ module SimulationCode = |> Seq.filter (fun (_,elements) -> not elements.IsEmpty) |> Seq.toList + /// The comment that is displayed at the top of generated files. + let internal autogenComment = [ + "//------------------------------------------------------------------------------" + "// " + "// This code was generated by a tool. " + "// Changes to this file may cause incorrect behavior and will be lost if " + "// the code is regenerated. " + "// " + "//------------------------------------------------------------------------------" + ] + // Builds the C# syntaxTree for the Q# elements defined in the given file. let buildSyntaxTree (fileName : NonNullable) (globalContext : CodegenContext) = let context = {globalContext with fileName = Some fileName.Value} @@ -1572,16 +1583,6 @@ module SimulationCode = let attributes = localElements |> List.map (snd >> buildDeclarationAttributes) |> List.concat let namespaces = localElements |> List.map (buildNamespace context) - let autogenComment = [ - "//------------------------------------------------------------------------------" - "// " - "// This code was generated by a tool. " - "// Changes to this file may cause incorrect behavior and will be lost if " - "// the code is regenerated. " - "// " - "//------------------------------------------------------------------------------" - ] - ``compilation unit`` attributes usings @@ -1608,6 +1609,3 @@ module SimulationCode = let generate fileName globalContext = buildSyntaxTree fileName globalContext |> formatSyntaxTree - - - \ No newline at end of file From b8e939f31943f952c3842d32974b52d8dbf32fda Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 8 Apr 2020 11:41:47 -0700 Subject: [PATCH 06/70] Add run method to runner --- src/Simulation/CsharpGeneration/EntryPoint.fs | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 79ba66fd5dc..3939078ff5d 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -1,4 +1,4 @@ -module Microsoft.Quantum.QsCompiler.CsharpGeneration.EntryPoint +module internal Microsoft.Quantum.QsCompiler.CsharpGeneration.EntryPoint open Microsoft.CodeAnalysis.CSharp.Syntax open Microsoft.Quantum.QsCompiler.SyntaxTokens @@ -15,7 +15,7 @@ let rec private getArguments context = function | QsTuple items -> items |> Seq.map (getArguments context) |> Seq.concat /// Returns a property containing a sequence of command-line options corresponding to each argument given. -let private getArgumentOptionsProperty arguments = +let private getArgumentOptionsProperty args = let optionTypeName = "System.CommandLine.Option" let optionsEnumerableTypeName = sprintf "System.Collections.Generic.IEnumerable<%s>" optionTypeName let getOption (name, typeName) = @@ -27,22 +27,45 @@ let private getArgumentOptionsProperty arguments = ``{`` [``ident`` "Required" <-- ``true``] ``}`` - let options = arguments |> Seq.map getOption |> Seq.toList + let options = args |> Seq.map getOption |> Seq.toList + ``property-arrow_get`` optionsEnumerableTypeName "Options" [``public``; ``static``] ``get`` (``=>`` (``new array`` (Some optionTypeName) options)) +/// Returns the name of the argument property for the given argument name. +let private getArgumentPropertyName (s : string) = + s.Substring(0, 1).ToUpper() + s.Substring 1 + /// Returns a sequence of properties corresponding to each argument given. let private getArgumentProperties = - let capitalize (s : string) = s.Substring(0, 1).ToUpper() + s.Substring 1 - Seq.map (fun (name, typeName) -> ``prop`` typeName (capitalize name) [``public``]) + Seq.map (fun (name, typeName) -> ``prop`` typeName (getArgumentPropertyName name) [``public``]) + +/// Returns the method for running the entry point using the argument properties declared in the runner. +let private getRunMethod context (entryPoint : QsCallable) = + let entryPointName = sprintf "%s.%s" entryPoint.FullName.Namespace.Value entryPoint.FullName.Name.Value + let argNames = getArguments context entryPoint.ArgumentTuple |> Seq.map fst + let returnTypeName = SimulationCode.roslynTypeName context entryPoint.Signature.ReturnType + let taskTypeName = sprintf "System.Threading.Tasks.Task<%s>" returnTypeName + let factoryArgName = "__factory__" + let callArgs : seq = + Seq.concat [ + Seq.singleton (upcast ``ident`` factoryArgName) + argNames |> Seq.map (fun name -> ``ident`` "this" <|.|> ``ident`` (getArgumentPropertyName name)) + ] + + ``arrow_method`` taskTypeName "Run" ``<<`` [] ``>>`` + ``(`` [``param`` factoryArgName ``of`` (``type`` "IOperationFactory")] ``)`` + [``public``; ``async``] + (Some (``=>`` (``await`` (``ident`` entryPointName <.> (``ident`` "Run", callArgs))))) /// Returns a C# class that can run the entry point using command-line options to provide the entry point's arguments. let private getEntryPointRunner context entryPoint = - let arguments = getArguments context entryPoint.ArgumentTuple + let args = getArguments context entryPoint.ArgumentTuple let members : seq = Seq.concat [ - Seq.singleton (upcast getArgumentOptionsProperty arguments) - getArgumentProperties arguments |> Seq.map (fun property -> upcast property) + Seq.singleton (upcast getArgumentOptionsProperty args) + getArgumentProperties args |> Seq.map (fun property -> upcast property) + Seq.singleton (upcast getRunMethod context entryPoint) ] ``class`` "__QsEntryPointRunner__" ``<<`` [] ``>>`` From 3b0f10f5c013225d50694dc0e2c8f97812969cf3 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 8 Apr 2020 11:56:19 -0700 Subject: [PATCH 07/70] Check if temp entry point exists in CodegenContext.Create --- src/Simulation/CsharpGeneration/Context.fs | 8 ++++++-- src/Simulation/CsharpGeneration/RewriteStep.fs | 8 +------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Simulation/CsharpGeneration/Context.fs b/src/Simulation/CsharpGeneration/Context.fs index f3981562479..3f2a92aa3d7 100644 --- a/src/Simulation/CsharpGeneration/Context.fs +++ b/src/Simulation/CsharpGeneration/Context.fs @@ -81,6 +81,11 @@ type CodegenContext = { if result.ContainsKey c.FullName.Name then result.[c.FullName.Name] <- (ns.Name, c) :: (result.[c.FullName.Name]) else result.[c.FullName.Name] <- [ns.Name, c]) result.ToImmutableDictionary() + + // TODO: Find the entry points automatically. See https://github.com/microsoft/qsharp-runtime/pull/163. + let defaultEntryPoints = + let helloQ = {Namespace = NonNullable<_>.New "QsSandbox"; Name = NonNullable<_>.New "HelloQ"} + if callables.ContainsKey helloQ then [helloQ] else [] { assemblyConstants = assemblyConstants @@ -92,8 +97,7 @@ type CodegenContext = { current = None fileName = None signature = None - // TODO: Find the entry points automatically. - entryPoints = [ {Namespace = NonNullable<_>.New "QsSandbox"; Name = NonNullable<_>.New "HelloQ"} ] + entryPoints = defaultEntryPoints } static member public Create syntaxTree = diff --git a/src/Simulation/CsharpGeneration/RewriteStep.fs b/src/Simulation/CsharpGeneration/RewriteStep.fs index b5414d54da7..53add2be535 100644 --- a/src/Simulation/CsharpGeneration/RewriteStep.fs +++ b/src/Simulation/CsharpGeneration/RewriteStep.fs @@ -45,14 +45,8 @@ type Emitter() = let content = SimulationCode.generate source context CompilationLoader.GeneratedFile(source, dir, ".g.cs", content) |> ignore - // TODO: There is not really any reason why this needs be tied to a particular source file, except - // GeneratedFile requires one. // TODO: Show diagnostic if there is more than one entry point. - // TODO: Remove filter once entry points are automatically populated. - let validEntryPoints = - context.entryPoints - |> Seq.filter (fun entryPoint -> context.allCallables.ContainsKey entryPoint) - match Seq.tryExactlyOne validEntryPoints with + match Seq.tryExactlyOne context.entryPoints with | Some entryPoint -> let callable = context.allCallables.[entryPoint] let content = EntryPoint.generate context callable From ba6fb11159d369b90d4085c187ac039e81e30ff5 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 8 Apr 2020 13:25:21 -0700 Subject: [PATCH 08/70] Get entry points from compilation --- src/Simulation/CsharpGeneration/Context.fs | 22 ++++++------------- .../CsharpGeneration/RewriteStep.fs | 3 ++- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/Simulation/CsharpGeneration/Context.fs b/src/Simulation/CsharpGeneration/Context.fs index 3f2a92aa3d7..ed2dc53ba57 100644 --- a/src/Simulation/CsharpGeneration/Context.fs +++ b/src/Simulation/CsharpGeneration/Context.fs @@ -68,27 +68,22 @@ type CodegenContext = { entryPoints : IEnumerable } with - static member public Create (syntaxTree, assemblyConstants) = + static member public Create (syntaxTree, ?assemblyConstants, ?entryPoints) = let udts = GlobalTypeResolutions syntaxTree let callables = GlobalCallableResolutions syntaxTree let positionInfos = DeclarationLocations.Accumulate syntaxTree - let callablesByName = + let callablesByName = let result = new Dictionary,(NonNullable*QsCallable) list>() syntaxTree |> Seq.collect (fun ns -> ns.Elements |> Seq.choose (function | QsCallable c -> Some (ns, c) | _ -> None)) - |> Seq.iter (fun (ns:QsNamespace,c:QsCallable) -> - if result.ContainsKey c.FullName.Name then result.[c.FullName.Name] <- (ns.Name, c) :: (result.[c.FullName.Name]) + |> Seq.iter (fun (ns:QsNamespace,c:QsCallable) -> + if result.ContainsKey c.FullName.Name then result.[c.FullName.Name] <- (ns.Name, c) :: (result.[c.FullName.Name]) else result.[c.FullName.Name] <- [ns.Name, c]) result.ToImmutableDictionary() - - // TODO: Find the entry points automatically. See https://github.com/microsoft/qsharp-runtime/pull/163. - let defaultEntryPoints = - let helloQ = {Namespace = NonNullable<_>.New "QsSandbox"; Name = NonNullable<_>.New "HelloQ"} - if callables.ContainsKey helloQ then [helloQ] else [] - { - assemblyConstants = assemblyConstants + { + assemblyConstants = defaultArg assemblyConstants (ImmutableDictionary.Empty :> IDictionary<_, _>) allQsElements = syntaxTree byName = callablesByName allUdts = udts @@ -97,12 +92,9 @@ type CodegenContext = { current = None fileName = None signature = None - entryPoints = defaultEntryPoints + entryPoints = defaultArg entryPoints (ImmutableArray.Empty :> IEnumerable<_>) } - static member public Create syntaxTree = - CodegenContext.Create(syntaxTree, ImmutableDictionary.Empty) - member public this.AssemblyName = match this.assemblyConstants.TryGetValue AssemblyConstants.AssemblyName with | true, name -> name diff --git a/src/Simulation/CsharpGeneration/RewriteStep.fs b/src/Simulation/CsharpGeneration/RewriteStep.fs index 53add2be535..39e2f92c765 100644 --- a/src/Simulation/CsharpGeneration/RewriteStep.fs +++ b/src/Simulation/CsharpGeneration/RewriteStep.fs @@ -36,7 +36,8 @@ type Emitter() = let dir = step.AssemblyConstants.TryGetValue AssemblyConstants.OutputPath |> function | true, outputFolder when outputFolder <> null -> Path.Combine(outputFolder, "src") | _ -> step.Name - let context = CodegenContext.Create (compilation.Namespaces, step.AssemblyConstants) + let context = + CodegenContext.Create (compilation.Namespaces, step.AssemblyConstants, compilation.EntryPoints) let allSources = GetSourceFiles.Apply compilation.Namespaces From 50b14bb9fdbf034e3cff9ba96c79a97744ba6067 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 8 Apr 2020 15:00:38 -0700 Subject: [PATCH 09/70] Add non-generated entry point driver source --- src/Simulation/CsharpGeneration/EntryPoint.fs | 40 +++++++--- .../Microsoft.Quantum.CsharpGeneration.fsproj | 1 + .../Resources/EntryPointDriver.cs | 75 +++++++++++++++++++ 3 files changed, 105 insertions(+), 11 deletions(-) create mode 100644 src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 3939078ff5d..7f08b3675de 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -4,6 +4,9 @@ open Microsoft.CodeAnalysis.CSharp.Syntax open Microsoft.Quantum.QsCompiler.SyntaxTokens open Microsoft.Quantum.QsCompiler.SyntaxTree open Microsoft.Quantum.RoslynWrapper +open System +open System.IO +open System.Reflection /// Returns a sequence of all of the named items in the argument tuple and their respective C# types. @@ -58,8 +61,11 @@ let private getRunMethod context (entryPoint : QsCallable) = [``public``; ``async``] (Some (``=>`` (``await`` (``ident`` entryPointName <.> (``ident`` "Run", callArgs))))) -/// Returns a C# class that can run the entry point using command-line options to provide the entry point's arguments. -let private getEntryPointRunner context entryPoint = +/// The name of the entry point runner class. +let private runnerClassName = "__QsEntryPointRunner__" + +/// Returns the class for running the entry point using command-line options to provide the entry point's arguments. +let private getRunnerClass context entryPoint = let args = getArguments context entryPoint.ArgumentTuple let members : seq = Seq.concat [ @@ -68,25 +74,37 @@ let private getEntryPointRunner context entryPoint = Seq.singleton (upcast getRunMethod context entryPoint) ] - ``class`` "__QsEntryPointRunner__" ``<<`` [] ``>>`` + ``class`` runnerClassName ``<<`` [] ``>>`` ``:`` None ``,`` [] [``internal``] ``{`` members ``}`` -/// Generates the C# source code for a standalone executable that runs the Q# entry point. -let internal generate context (entryPoint : QsCallable) = +/// Returns the source code for the entry point runner. +let private getRunner context (entryPoint : QsCallable) = let ns = ``namespace`` entryPoint.FullName.Namespace.Value ``{`` - [] - [getEntryPointRunner context entryPoint] + (Seq.map ``using`` SimulationCode.autoNamespaces) + [getRunnerClass context entryPoint] ``}`` - ``compilation unit`` - [] - (Seq.map ``using`` SimulationCode.autoNamespaces) - [ns] + ``compilation unit`` [] [] [ns] |> ``with leading comments`` SimulationCode.autogenComment |> SimulationCode.formatSyntaxTree + +/// Returns the source code for the entry point driver. +let private getDriver (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", entryPoint.FullName.Namespace.Value) + .Replace("@SimulatorKind", "__QsSimulatorKind__") + .Replace("@EntryPointDriver", "__QsEntryPointDriver__") + .Replace("@EntryPointRunner", runnerClassName) + +/// Generates C# source code for a standalone executable that runs the Q# entry point. +let internal generate context entryPoint = + getRunner context entryPoint + Environment.NewLine + getDriver entryPoint diff --git a/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj b/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj index 97619bfec4c..6379ca7d96a 100644 --- a/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj +++ b/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj @@ -8,6 +8,7 @@ + diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs new file mode 100644 index 00000000000..4c8ba5bd808 --- /dev/null +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -0,0 +1,75 @@ +namespace @Namespace +{ + using Microsoft.Quantum.Simulation.Core; + using Microsoft.Quantum.Simulation.Simulators; + using System; + using System.CommandLine; + using System.CommandLine.Invocation; + using System.CommandLine.Parsing; + using System.Threading.Tasks; + + internal enum @SimulatorKind + { + FullState, + Toffoli + } + + internal static class @EntryPointDriver + { + private static async Task Main(string[] args) + { + var simulate = new Command("simulate") + { + Description = "Run the program using a local simulator.", + Handler = CommandHandler.Create<@EntryPointRunner, @SimulatorKind>(Simulate) + }; + simulate.AddOption(new Option<@SimulatorKind>( + new[] { "--simulator", "-s" }, + () => @SimulatorKind.FullState, + $"The name of the simulator to use.")); + + var resources = new Command("resources") + { + Description = "Estimate the resource usage of the program.", + Handler = CommandHandler.Create<@EntryPointRunner>(Resources) + }; + + var root = new RootCommand() { simulate, resources }; + foreach (var option in @EntryPointRunner.Options) + { + root.AddGlobalOption(option); + } + root.AddValidator(result => "A command is required."); + return await root.InvokeAsync(args); + } + + private static async Task Simulate(@EntryPointRunner entryPoint, @SimulatorKind simulator) + { + var result = await WithSimulator(entryPoint.Run, simulator); + Console.WriteLine(result); + } + + private static async Task Resources(@EntryPointRunner entryPoint) + { + var estimator = new ResourcesEstimator(); + await entryPoint.Run(estimator); + Console.Write(estimator.ToTSV()); + } + + private static async Task WithSimulator(Func> callable, @SimulatorKind simulator) + { + switch (simulator) + { + case @SimulatorKind.FullState: + using (var quantumSimulator = new QuantumSimulator()) + { + return await callable(quantumSimulator); + } + case @SimulatorKind.Toffoli: + return await callable(new ToffoliSimulator()); + default: + throw new ArgumentException("Invalid simulator."); + } + } + } +} From b9010cc1db01a0fb4c0390e73dc53832d0b108ad Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 8 Apr 2020 15:47:36 -0700 Subject: [PATCH 10/70] Use IEnumerable for array types --- src/Simulation/CsharpGeneration/EntryPoint.fs | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 7f08b3675de..a5193354306 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -9,22 +9,27 @@ open System.IO open System.Reflection -/// Returns a sequence of all of the named items in the argument tuple and their respective C# types. +/// Returns a sequence of all of the named items in the argument tuple and their respective C# and Q# types. let rec private getArguments context = function | QsTupleItem variable -> - match variable.VariableName with - | ValidName name -> Seq.singleton (name.Value, SimulationCode.roslynTypeName context variable.Type) - | InvalidName -> Seq.empty + 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.Value, typeName, variable.Type) + | ValidName name, _ -> + Seq.singleton (name.Value, SimulationCode.roslynTypeName context variable.Type, variable.Type) + | InvalidName, _ -> Seq.empty | QsTuple items -> items |> Seq.map (getArguments context) |> Seq.concat /// Returns a property containing a sequence of command-line options corresponding to each argument given. let private getArgumentOptionsProperty args = let optionTypeName = "System.CommandLine.Option" let optionsEnumerableTypeName = sprintf "System.Collections.Generic.IEnumerable<%s>" optionTypeName - let getOption (name, typeName) = + let getOption (name, typeName, _) = // TODO: Generate diagnostic if argument option name conflicts with a standard option name. // TODO: Use kebab-case. - // TODO: We might need to convert IQArray to a standard array type. let optionName = "--" + name ``new init`` (``type`` [sprintf "%s<%s>" optionTypeName typeName]) ``(`` [``literal`` optionName] ``)`` ``{`` @@ -41,19 +46,28 @@ let private getArgumentPropertyName (s : string) = /// Returns a sequence of properties corresponding to each argument given. let private getArgumentProperties = - Seq.map (fun (name, typeName) -> ``prop`` typeName (getArgumentPropertyName name) [``public``]) + Seq.map (fun (name, typeName, _) -> ``prop`` typeName (getArgumentPropertyName name) [``public``]) /// Returns the method for running the entry point using the argument properties declared in the runner. let private getRunMethod context (entryPoint : QsCallable) = let entryPointName = sprintf "%s.%s" entryPoint.FullName.Namespace.Value entryPoint.FullName.Name.Value - let argNames = getArguments context entryPoint.ArgumentTuple |> Seq.map fst let returnTypeName = SimulationCode.roslynTypeName context entryPoint.Signature.ReturnType let taskTypeName = sprintf "System.Threading.Tasks.Task<%s>" returnTypeName let factoryArgName = "__factory__" + + let getArgExpr (name, _, qsType : ResolvedType) = + let property = ``ident`` "this" <|.|> ``ident`` (getArgumentPropertyName 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 callArgs : seq = Seq.concat [ Seq.singleton (upcast ``ident`` factoryArgName) - argNames |> Seq.map (fun name -> ``ident`` "this" <|.|> ``ident`` (getArgumentPropertyName name)) + Seq.map getArgExpr (getArguments context entryPoint.ArgumentTuple) ] ``arrow_method`` taskTypeName "Run" ``<<`` [] ``>>`` From 41d599bd88677735980b85deb44c6e1ff84de5a1 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 8 Apr 2020 16:07:35 -0700 Subject: [PATCH 11/70] Convert argument names to kebab-case --- src/Simulation/CsharpGeneration/EntryPoint.fs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index a5193354306..cf18fa4ea65 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -29,9 +29,9 @@ let private getArgumentOptionsProperty args = let optionsEnumerableTypeName = sprintf "System.Collections.Generic.IEnumerable<%s>" optionTypeName let getOption (name, typeName, _) = // TODO: Generate diagnostic if argument option name conflicts with a standard option name. - // TODO: Use kebab-case. - let optionName = "--" + name - ``new init`` (``type`` [sprintf "%s<%s>" optionTypeName typeName]) ``(`` [``literal`` optionName] ``)`` + let toKebabCaseIdent = ``ident`` "System.CommandLine.Parsing.StringExtensions.ToKebabCase" + let nameExpr = ``literal`` "--" <+> ``invoke`` toKebabCaseIdent ``(`` [``literal`` name] ``)`` + ``new init`` (``type`` [sprintf "%s<%s>" optionTypeName typeName]) ``(`` [nameExpr] ``)`` ``{`` [``ident`` "Required" <-- ``true``] ``}`` From 75e5a7c227913cda828178ec3a0953b38b79ba98 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 8 Apr 2020 19:23:48 -0700 Subject: [PATCH 12/70] Undo changes to CodegenContext --- src/Simulation/CsharpGeneration/Context.fs | 29 ++++++++++--------- .../CsharpGeneration/RewriteStep.fs | 5 ++-- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Simulation/CsharpGeneration/Context.fs b/src/Simulation/CsharpGeneration/Context.fs index ed2dc53ba57..b9f93661907 100644 --- a/src/Simulation/CsharpGeneration/Context.fs +++ b/src/Simulation/CsharpGeneration/Context.fs @@ -65,36 +65,37 @@ type CodegenContext = { current : QsQualifiedName option signature : ResolvedSignature option fileName : string option - entryPoints : IEnumerable } with - static member public Create (syntaxTree, ?assemblyConstants, ?entryPoints) = + static member public Create (syntaxTree, assemblyConstants) = let udts = GlobalTypeResolutions syntaxTree let callables = GlobalCallableResolutions syntaxTree let positionInfos = DeclarationLocations.Accumulate syntaxTree - let callablesByName = + let callablesByName = let result = new Dictionary,(NonNullable*QsCallable) list>() syntaxTree |> Seq.collect (fun ns -> ns.Elements |> Seq.choose (function | QsCallable c -> Some (ns, c) | _ -> None)) - |> Seq.iter (fun (ns:QsNamespace,c:QsCallable) -> - if result.ContainsKey c.FullName.Name then result.[c.FullName.Name] <- (ns.Name, c) :: (result.[c.FullName.Name]) + |> Seq.iter (fun (ns:QsNamespace,c:QsCallable) -> + if result.ContainsKey c.FullName.Name then result.[c.FullName.Name] <- (ns.Name, c) :: (result.[c.FullName.Name]) else result.[c.FullName.Name] <- [ns.Name, c]) result.ToImmutableDictionary() - { - assemblyConstants = defaultArg assemblyConstants (ImmutableDictionary.Empty :> IDictionary<_, _>) - allQsElements = syntaxTree - byName = callablesByName - allUdts = udts - allCallables = callables + { + assemblyConstants = assemblyConstants; + allQsElements = syntaxTree; + byName = callablesByName; + allUdts = udts; + allCallables = callables; declarationPositions = positionInfos.ToImmutableDictionary((fun g -> g.Key), (fun g -> g.ToImmutableSortedSet())) - current = None - fileName = None + current = None; + fileName = None; signature = None - entryPoints = defaultArg entryPoints (ImmutableArray.Empty :> IEnumerable<_>) } + static member public Create syntaxTree = + CodegenContext.Create(syntaxTree, ImmutableDictionary.Empty) + member public this.AssemblyName = match this.assemblyConstants.TryGetValue AssemblyConstants.AssemblyName with | true, name -> name diff --git a/src/Simulation/CsharpGeneration/RewriteStep.fs b/src/Simulation/CsharpGeneration/RewriteStep.fs index 39e2f92c765..a0e29c57f04 100644 --- a/src/Simulation/CsharpGeneration/RewriteStep.fs +++ b/src/Simulation/CsharpGeneration/RewriteStep.fs @@ -36,8 +36,7 @@ type Emitter() = let dir = step.AssemblyConstants.TryGetValue AssemblyConstants.OutputPath |> function | true, outputFolder when outputFolder <> null -> Path.Combine(outputFolder, "src") | _ -> step.Name - let context = - CodegenContext.Create (compilation.Namespaces, step.AssemblyConstants, compilation.EntryPoints) + let context = CodegenContext.Create (compilation.Namespaces, step.AssemblyConstants) let allSources = GetSourceFiles.Apply compilation.Namespaces @@ -47,7 +46,7 @@ type Emitter() = CompilationLoader.GeneratedFile(source, dir, ".g.cs", content) |> ignore // TODO: Show diagnostic if there is more than one entry point. - match Seq.tryExactlyOne context.entryPoints with + match Seq.tryExactlyOne compilation.EntryPoints with | Some entryPoint -> let callable = context.allCallables.[entryPoint] let content = EntryPoint.generate context callable From 737ed37c7b559adeb73a3c19d04b7e6ecdc0e485 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Thu, 9 Apr 2020 12:00:19 -0700 Subject: [PATCH 13/70] Remove unnecessary string interpolation --- src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index 4c8ba5bd808..9367472e61e 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -26,7 +26,7 @@ private static async Task Main(string[] args) simulate.AddOption(new Option<@SimulatorKind>( new[] { "--simulator", "-s" }, () => @SimulatorKind.FullState, - $"The name of the simulator to use.")); + "The name of the simulator to use.")); var resources = new Command("resources") { From a2dc23a91742c3b82bb32d8d21e156cb3520a84b Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Thu, 9 Apr 2020 15:15:22 -0700 Subject: [PATCH 14/70] Use entry point summary as description for root command --- src/Simulation/CsharpGeneration/EntryPoint.fs | 27 +++++++++++-------- .../Resources/EntryPointDriver.cs | 12 ++++----- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index cf18fa4ea65..d9fee1ec835 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -1,6 +1,7 @@ module internal Microsoft.Quantum.QsCompiler.CsharpGeneration.EntryPoint open Microsoft.CodeAnalysis.CSharp.Syntax +open Microsoft.Quantum.QsCompiler.SyntaxProcessing.SyntaxExtensions open Microsoft.Quantum.QsCompiler.SyntaxTokens open Microsoft.Quantum.QsCompiler.SyntaxTree open Microsoft.Quantum.RoslynWrapper @@ -48,7 +49,7 @@ let private getArgumentPropertyName (s : string) = let private getArgumentProperties = Seq.map (fun (name, typeName, _) -> ``prop`` typeName (getArgumentPropertyName name) [``public``]) -/// Returns the method for running the entry point using the argument properties declared in the runner. +/// Returns the method for running the entry point using the argument properties declared in the adapter. let private getRunMethod context (entryPoint : QsCallable) = let entryPointName = sprintf "%s.%s" entryPoint.FullName.Namespace.Value entryPoint.FullName.Name.Value let returnTypeName = SimulationCode.roslynTypeName context entryPoint.Signature.ReturnType @@ -75,33 +76,37 @@ let private getRunMethod context (entryPoint : QsCallable) = [``public``; ``async``] (Some (``=>`` (``await`` (``ident`` entryPointName <.> (``ident`` "Run", callArgs))))) -/// The name of the entry point runner class. -let private runnerClassName = "__QsEntryPointRunner__" +/// The name of the entry point adapter class. +let private adapterClassName = "__QsEntryPointAdapter__" -/// Returns the class for running the entry point using command-line options to provide the entry point's arguments. -let private getRunnerClass context entryPoint = +/// Returns the class that adapts the entry point for use with the command-line parsing library and the driver. +let private getAdapterClass context (entryPoint : QsCallable) = + let summary = + ``property-arrow_get`` "string" "Summary" [``public``; ``static``] + ``get`` (``=>`` (``literal`` ((PrintSummary entryPoint.Documentation false).Trim ()))) let args = getArguments context entryPoint.ArgumentTuple let members : seq = Seq.concat [ + Seq.singleton (upcast summary) Seq.singleton (upcast getArgumentOptionsProperty args) getArgumentProperties args |> Seq.map (fun property -> upcast property) Seq.singleton (upcast getRunMethod context entryPoint) ] - ``class`` runnerClassName ``<<`` [] ``>>`` + ``class`` adapterClassName ``<<`` [] ``>>`` ``:`` None ``,`` [] [``internal``] ``{`` members ``}`` -/// Returns the source code for the entry point runner. -let private getRunner context (entryPoint : QsCallable) = +/// Returns the source code for the entry point adapter. +let private getAdapter context (entryPoint : QsCallable) = let ns = ``namespace`` entryPoint.FullName.Namespace.Value ``{`` (Seq.map ``using`` SimulationCode.autoNamespaces) - [getRunnerClass context entryPoint] + [getAdapterClass context entryPoint] ``}`` ``compilation unit`` [] [] [ns] @@ -117,8 +122,8 @@ let private getDriver (entryPoint : QsCallable) = .Replace("@Namespace", entryPoint.FullName.Namespace.Value) .Replace("@SimulatorKind", "__QsSimulatorKind__") .Replace("@EntryPointDriver", "__QsEntryPointDriver__") - .Replace("@EntryPointRunner", runnerClassName) + .Replace("@EntryPointAdapter", adapterClassName) /// Generates C# source code for a standalone executable that runs the Q# entry point. let internal generate context entryPoint = - getRunner context entryPoint + Environment.NewLine + getDriver entryPoint + getAdapter context entryPoint + Environment.NewLine + getDriver entryPoint diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index 9367472e61e..55c6c1bf580 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -21,7 +21,7 @@ private static async Task Main(string[] args) var simulate = new Command("simulate") { Description = "Run the program using a local simulator.", - Handler = CommandHandler.Create<@EntryPointRunner, @SimulatorKind>(Simulate) + Handler = CommandHandler.Create<@EntryPointAdapter, @SimulatorKind>(Simulate) }; simulate.AddOption(new Option<@SimulatorKind>( new[] { "--simulator", "-s" }, @@ -31,11 +31,11 @@ private static async Task Main(string[] args) var resources = new Command("resources") { Description = "Estimate the resource usage of the program.", - Handler = CommandHandler.Create<@EntryPointRunner>(Resources) + Handler = CommandHandler.Create<@EntryPointAdapter>(Resources) }; - var root = new RootCommand() { simulate, resources }; - foreach (var option in @EntryPointRunner.Options) + var root = new RootCommand(@EntryPointAdapter.Summary) { simulate, resources }; + foreach (var option in @EntryPointAdapter.Options) { root.AddGlobalOption(option); } @@ -43,13 +43,13 @@ private static async Task Main(string[] args) return await root.InvokeAsync(args); } - private static async Task Simulate(@EntryPointRunner entryPoint, @SimulatorKind simulator) + private static async Task Simulate(@EntryPointAdapter entryPoint, @SimulatorKind simulator) { var result = await WithSimulator(entryPoint.Run, simulator); Console.WriteLine(result); } - private static async Task Resources(@EntryPointRunner entryPoint) + private static async Task Resources(@EntryPointAdapter entryPoint) { var estimator = new ResourcesEstimator(); await entryPoint.Run(estimator); From 1799688e2212b85624a129eedd153004337df8ee Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Thu, 9 Apr 2020 15:50:15 -0700 Subject: [PATCH 15/70] Use doc comment for option descriptions --- src/Simulation/CsharpGeneration/EntryPoint.fs | 66 +++++++++++-------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index d9fee1ec835..01009666d49 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -10,54 +10,68 @@ open System.IO open System.Reflection -/// Returns a sequence of all of the named items in the argument tuple and their respective C# and Q# types. -let rec private getArguments context = function +/// An entry point parameter. +type private Parameter = + { Name : string + QsharpType : ResolvedType + CsharpTypeName : string + Description : string } + +/// Returns a sequence of all of the named parameters in the argument tuple and their respective C# and Q# types. +let rec private getParameters 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.Value, typeName, variable.Type) + Seq.singleton { Name = name.Value + QsharpType = variable.Type + CsharpTypeName = typeName + Description = ParameterDescription doc name.Value } | ValidName name, _ -> - Seq.singleton (name.Value, SimulationCode.roslynTypeName context variable.Type, variable.Type) + Seq.singleton { Name = name.Value + QsharpType = variable.Type + CsharpTypeName = SimulationCode.roslynTypeName context variable.Type + Description = ParameterDescription doc name.Value } | InvalidName, _ -> Seq.empty - | QsTuple items -> items |> Seq.map (getArguments context) |> Seq.concat + | QsTuple items -> items |> Seq.map (getParameters context doc) |> Seq.concat -/// Returns a property containing a sequence of command-line options corresponding to each argument given. -let private getArgumentOptionsProperty args = +/// Returns a property containing a sequence of command-line options corresponding to each parameter given. +let private getParameterOptionsProperty parameters = let optionTypeName = "System.CommandLine.Option" let optionsEnumerableTypeName = sprintf "System.Collections.Generic.IEnumerable<%s>" optionTypeName - let getOption (name, typeName, _) = - // TODO: Generate diagnostic if argument option name conflicts with a standard option name. + let getOption { Name = name; CsharpTypeName = typeName; Description = desc } = + // TODO: Generate diagnostic if the parameter option name conflicts with a standard option name. let toKebabCaseIdent = ``ident`` "System.CommandLine.Parsing.StringExtensions.ToKebabCase" let nameExpr = ``literal`` "--" <+> ``invoke`` toKebabCaseIdent ``(`` [``literal`` name] ``)`` - ``new init`` (``type`` [sprintf "%s<%s>" optionTypeName typeName]) ``(`` [nameExpr] ``)`` + ``new init`` (``type`` [sprintf "%s<%s>" optionTypeName typeName]) ``(`` [nameExpr; ``literal`` desc] ``)`` ``{`` [``ident`` "Required" <-- ``true``] ``}`` - let options = args |> Seq.map getOption |> Seq.toList + let options = parameters |> Seq.map getOption |> Seq.toList ``property-arrow_get`` optionsEnumerableTypeName "Options" [``public``; ``static``] ``get`` (``=>`` (``new array`` (Some optionTypeName) options)) -/// Returns the name of the argument property for the given argument name. -let private getArgumentPropertyName (s : string) = +/// Returns the name of the parameter property for the given parameter name. +let private getParameterPropertyName (s : string) = s.Substring(0, 1).ToUpper() + s.Substring 1 -/// Returns a sequence of properties corresponding to each argument given. -let private getArgumentProperties = - Seq.map (fun (name, typeName, _) -> ``prop`` typeName (getArgumentPropertyName name) [``public``]) +/// Returns a sequence of properties corresponding to each parameter given. +let private getParameterProperties = + Seq.map (fun { Name = name; CsharpTypeName = typeName } -> + ``prop`` typeName (getParameterPropertyName name) [``public``]) -/// Returns the method for running the entry point using the argument properties declared in the adapter. +/// Returns the method for running the entry point using the parameter properties declared in the adapter. let private getRunMethod context (entryPoint : QsCallable) = let entryPointName = sprintf "%s.%s" entryPoint.FullName.Namespace.Value entryPoint.FullName.Name.Value let returnTypeName = SimulationCode.roslynTypeName context entryPoint.Signature.ReturnType let taskTypeName = sprintf "System.Threading.Tasks.Task<%s>" returnTypeName - let factoryArgName = "__factory__" + let factoryParamName = "__factory__" - let getArgExpr (name, _, qsType : ResolvedType) = - let property = ``ident`` "this" <|.|> ``ident`` (getArgumentPropertyName name) + let getArgExpr { Name = name; QsharpType = qsType } = + let property = ``ident`` "this" <|.|> ``ident`` (getParameterPropertyName name) match qsType.Resolution with | ArrayType itemType -> // Convert the IEnumerable property into a QArray. @@ -67,12 +81,12 @@ let private getRunMethod context (entryPoint : QsCallable) = let callArgs : seq = Seq.concat [ - Seq.singleton (upcast ``ident`` factoryArgName) - Seq.map getArgExpr (getArguments context entryPoint.ArgumentTuple) + Seq.singleton (upcast ``ident`` factoryParamName) + Seq.map getArgExpr (getParameters context entryPoint.Documentation entryPoint.ArgumentTuple) ] ``arrow_method`` taskTypeName "Run" ``<<`` [] ``>>`` - ``(`` [``param`` factoryArgName ``of`` (``type`` "IOperationFactory")] ``)`` + ``(`` [``param`` factoryParamName ``of`` (``type`` "IOperationFactory")] ``)`` [``public``; ``async``] (Some (``=>`` (``await`` (``ident`` entryPointName <.> (``ident`` "Run", callArgs))))) @@ -84,12 +98,12 @@ let private getAdapterClass context (entryPoint : QsCallable) = let summary = ``property-arrow_get`` "string" "Summary" [``public``; ``static``] ``get`` (``=>`` (``literal`` ((PrintSummary entryPoint.Documentation false).Trim ()))) - let args = getArguments context entryPoint.ArgumentTuple + let parameters = getParameters context entryPoint.Documentation entryPoint.ArgumentTuple let members : seq = Seq.concat [ Seq.singleton (upcast summary) - Seq.singleton (upcast getArgumentOptionsProperty args) - getArgumentProperties args |> Seq.map (fun property -> upcast property) + Seq.singleton (upcast getParameterOptionsProperty parameters) + getParameterProperties parameters |> Seq.map (fun property -> upcast property) Seq.singleton (upcast getRunMethod context entryPoint) ] From 913b265f5898bef804fc10c413db2990c33a8f30 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Thu, 9 Apr 2020 19:40:13 -0700 Subject: [PATCH 16/70] One-letter parameters get one dash --- src/Simulation/CsharpGeneration/EntryPoint.fs | 8 +++++--- src/Simulation/CsharpGeneration/RewriteStep.fs | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 01009666d49..3a99d22a552 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -41,10 +41,12 @@ let rec private getParameters context doc = function let private getParameterOptionsProperty parameters = let optionTypeName = "System.CommandLine.Option" let optionsEnumerableTypeName = sprintf "System.Collections.Generic.IEnumerable<%s>" optionTypeName + let toKebabCaseIdent = ``ident`` "System.CommandLine.Parsing.StringExtensions.ToKebabCase" let getOption { Name = name; CsharpTypeName = typeName; Description = desc } = - // TODO: Generate diagnostic if the parameter option name conflicts with a standard option name. - let toKebabCaseIdent = ``ident`` "System.CommandLine.Parsing.StringExtensions.ToKebabCase" - let nameExpr = ``literal`` "--" <+> ``invoke`` toKebabCaseIdent ``(`` [``literal`` name] ``)`` + let nameExpr = + if name.Length = 1 + then ``literal`` ("-" + name) + else ``literal`` "--" <+> ``invoke`` toKebabCaseIdent ``(`` [``literal`` name] ``)`` ``new init`` (``type`` [sprintf "%s<%s>" optionTypeName typeName]) ``(`` [nameExpr; ``literal`` desc] ``)`` ``{`` [``ident`` "Required" <-- ``true``] diff --git a/src/Simulation/CsharpGeneration/RewriteStep.fs b/src/Simulation/CsharpGeneration/RewriteStep.fs index a0e29c57f04..a9d331eae62 100644 --- a/src/Simulation/CsharpGeneration/RewriteStep.fs +++ b/src/Simulation/CsharpGeneration/RewriteStep.fs @@ -45,7 +45,6 @@ type Emitter() = let content = SimulationCode.generate source context CompilationLoader.GeneratedFile(source, dir, ".g.cs", content) |> ignore - // TODO: Show diagnostic if there is more than one entry point. match Seq.tryExactlyOne compilation.EntryPoints with | Some entryPoint -> let callable = context.allCallables.[entryPoint] From 17b122ebf506d405dad9e1ba98bb52c41e5ef60e Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Fri, 10 Apr 2020 18:21:48 -0700 Subject: [PATCH 17/70] Don't output anything if return type is Unit --- .../CsharpGeneration/Resources/EntryPointDriver.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index 55c6c1bf580..628db68219e 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -46,7 +46,12 @@ private static async Task Main(string[] args) private static async Task Simulate(@EntryPointAdapter entryPoint, @SimulatorKind simulator) { var result = await WithSimulator(entryPoint.Run, simulator); - Console.WriteLine(result); +#pragma warning disable CS0184 + if (!(result is QVoid)) +#pragma warning restore CS0184 + { + Console.WriteLine(result); + } } private static async Task Resources(@EntryPointAdapter entryPoint) From 93f8867e2f4b218e7aec01c6ba7b1c28e1b4da54 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 14 Apr 2020 00:04:03 -0700 Subject: [PATCH 18/70] Handle parameters of type Range --- src/Simulation/CsharpGeneration/EntryPoint.fs | 39 +++++-- .../Resources/EntryPointDriver.cs | 102 +++++++++++++++--- 2 files changed, 121 insertions(+), 20 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 3a99d22a552..276f2a8b3b7 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -17,6 +17,21 @@ type private Parameter = CsharpTypeName : string Description : string } +/// The name of the entry point adapter class. +let private adapterClassName = "__QsEntryPointAdapter__" + +/// The name of the entry point driver class. +let private driverClassName = "__QsEntryPointDriver__" + +/// The name of the result struct. +let private resultStructName = "__QsResult__" + +/// The name of the class containing extension methods for the result struct. +let private resultExtensionsClassName = "__QsResultExtensions__" + +/// The name of the property containing the argument handler for ranges. +let private rangeHandlerPropertyName = "RangeArgumentHandler" + /// Returns a sequence of all of the named parameters in the argument tuple and their respective C# and Q# types. let rec private getParameters context doc = function | QsTupleItem variable -> @@ -37,22 +52,33 @@ let rec private getParameters context doc = function | InvalidName, _ -> Seq.empty | QsTuple items -> items |> Seq.map (getParameters context doc) |> Seq.concat +/// Returns the custom argument handler for the given Q# type. +let private getArgumentHandler = function + | Range -> ``ident`` driverClassName <|.|> ``ident`` rangeHandlerPropertyName |> Some + | _ -> None + /// Returns a property containing a sequence of command-line options corresponding to each parameter given. let private getParameterOptionsProperty parameters = let optionTypeName = "System.CommandLine.Option" let optionsEnumerableTypeName = sprintf "System.Collections.Generic.IEnumerable<%s>" optionTypeName let toKebabCaseIdent = ``ident`` "System.CommandLine.Parsing.StringExtensions.ToKebabCase" - let getOption { Name = name; CsharpTypeName = typeName; Description = desc } = + let getOption { Name = name; QsharpType = qsType; CsharpTypeName = typeName; Description = desc } = let nameExpr = if name.Length = 1 then ``literal`` ("-" + name) else ``literal`` "--" <+> ``invoke`` toKebabCaseIdent ``(`` [``literal`` name] ``)`` + let members = + getArgumentHandler qsType.Resolution + |> Option.map (fun handler -> ``ident`` "Argument" <-- handler :> ExpressionSyntax) + |> Option.toList + |> List.append [``ident`` "Required" <-- ``true``] + ``new init`` (``type`` [sprintf "%s<%s>" optionTypeName typeName]) ``(`` [nameExpr; ``literal`` desc] ``)`` ``{`` - [``ident`` "Required" <-- ``true``] + members ``}`` - let options = parameters |> Seq.map getOption |> Seq.toList + let options = parameters |> Seq.map getOption |> Seq.toList ``property-arrow_get`` optionsEnumerableTypeName "Options" [``public``; ``static``] ``get`` (``=>`` (``new array`` (Some optionTypeName) options)) @@ -92,9 +118,6 @@ let private getRunMethod context (entryPoint : QsCallable) = [``public``; ``async``] (Some (``=>`` (``await`` (``ident`` entryPointName <.> (``ident`` "Run", callArgs))))) -/// The name of the entry point adapter class. -let private adapterClassName = "__QsEntryPointAdapter__" - /// Returns the class that adapts the entry point for use with the command-line parsing library and the driver. let private getAdapterClass context (entryPoint : QsCallable) = let summary = @@ -136,9 +159,11 @@ let private getDriver (entryPoint : QsCallable) = use reader = new StreamReader(stream) reader.ReadToEnd() .Replace("@Namespace", entryPoint.FullName.Namespace.Value) - .Replace("@SimulatorKind", "__QsSimulatorKind__") .Replace("@EntryPointDriver", "__QsEntryPointDriver__") .Replace("@EntryPointAdapter", adapterClassName) + .Replace("@RangeArgumentHandler", rangeHandlerPropertyName) + .Replace("@ResultExtensions", resultExtensionsClassName) + .Replace("@Result", resultStructName) /// 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/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index 628db68219e..fc1add386ec 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -3,29 +3,41 @@ using Microsoft.Quantum.Simulation.Core; using Microsoft.Quantum.Simulation.Simulators; using System; + using System.Collections.Generic; using System.CommandLine; using System.CommandLine.Invocation; using System.CommandLine.Parsing; + using System.Linq; using System.Threading.Tasks; - internal enum @SimulatorKind - { - FullState, - Toffoli - } - internal static class @EntryPointDriver { + internal static readonly Argument @RangeArgumentHandler = + new Argument(result => + { + var option = (result.Parent as OptionResult)?.Token.Value ?? result.Argument.Name; + var arg = string.Join(' ', result.Tokens.Select(token => token.Value)); + return new[] + { + ParseRangeFromEnumerable(option, arg, result.Tokens.Select(token => token.Value)), + ParseRangeFromEnumerable(option, arg, arg.Split("..", StringSplitOptions.RemoveEmptyEntries)) + } + .Choose(errors => result.ErrorMessage = string.Join('\n', errors)); + }) + { + Arity = ArgumentArity.OneOrMore + }; + private static async Task Main(string[] args) { var simulate = new Command("simulate") { Description = "Run the program using a local simulator.", - Handler = CommandHandler.Create<@EntryPointAdapter, @SimulatorKind>(Simulate) + Handler = CommandHandler.Create<@EntryPointAdapter, SimulatorKind>(Simulate) }; - simulate.AddOption(new Option<@SimulatorKind>( + simulate.AddOption(new Option( new[] { "--simulator", "-s" }, - () => @SimulatorKind.FullState, + () => SimulatorKind.FullState, "The name of the simulator to use.")); var resources = new Command("resources") @@ -43,7 +55,7 @@ private static async Task Main(string[] args) return await root.InvokeAsync(args); } - private static async Task Simulate(@EntryPointAdapter entryPoint, @SimulatorKind simulator) + private static async Task Simulate(@EntryPointAdapter entryPoint, SimulatorKind simulator) { var result = await WithSimulator(entryPoint.Run, simulator); #pragma warning disable CS0184 @@ -61,20 +73,84 @@ private static async Task Resources(@EntryPointAdapter entryPoint) Console.Write(estimator.ToTSV()); } - private static async Task WithSimulator(Func> callable, @SimulatorKind simulator) + private static async Task WithSimulator(Func> callable, SimulatorKind simulator) { switch (simulator) { - case @SimulatorKind.FullState: + case SimulatorKind.FullState: using (var quantumSimulator = new QuantumSimulator()) { return await callable(quantumSimulator); } - case @SimulatorKind.Toffoli: + case SimulatorKind.Toffoli: return await callable(new ToffoliSimulator()); default: throw new ArgumentException("Invalid simulator."); } } + + 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( + $"Cannot parse argument '{arg}' for option '{option}' as expected type {typeof(QRange)}.")); + + private static @Result TryParseLong(string option, string str) => + long.TryParse(str, out var result) + ? @Result.Success(result) + : @Result.Failure( + $"Cannot parse argument '{str}' for option '{option}' as expected type {typeof(long)}."); + + private enum SimulatorKind + { + FullState, + Toffoli + } + } + + internal struct @Result + { + public bool IsSuccess { get; } + public T Value { get; } + public bool IsFailure { get => !IsSuccess; } + public string ErrorMessage { get; } + + private @Result(bool isSuccess, T value, string errorMessage) + { + IsSuccess = isSuccess; + Value = value; + ErrorMessage = errorMessage; + } + + public static @Result Success(T value) => new @Result(true, value, default); + + public static @Result Failure(string errorMessage) => new @Result(false, default, errorMessage); + } + + internal static class @ResultExtensions + { + internal static @Result Bind(this @Result result, Func> binder) => + result.IsFailure ? @Result.Failure(result.ErrorMessage) : binder(result.Value); + + internal static @Result> Sequence(this IEnumerable<@Result> results) => + results.All(result => result.IsSuccess) + ? @Result>.Success(results.Select(results => results.Value)) + : @Result>.Failure(results.First(results => results.IsFailure).ErrorMessage); + + internal static T Choose(this IEnumerable<@Result> results, Action> onError) + { + if (results.Any(result => result.IsSuccess)) + { + return results.First(attempt => attempt.IsSuccess).Value; + } + else + { + onError(results.Where(result => result.IsFailure).Select(result => result.ErrorMessage)); + return default; + } + } } } From 7abfca227dec0a5e766db18b891425e2dd216f2a Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 14 Apr 2020 00:51:39 -0700 Subject: [PATCH 19/70] Add doc comments to driver --- .../Resources/EntryPointDriver.cs | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index fc1add386ec..059e2fb46c8 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -10,8 +10,14 @@ 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 @EntryPointDriver { + /// + /// The argument handler for the Q# range type. + /// internal static readonly Argument @RangeArgumentHandler = new Argument(result => { @@ -28,6 +34,11 @@ internal static class @EntryPointDriver Arity = ArgumentArity.OneOrMore }; + /// + /// Runs the entry point. + /// + /// The command-line arguments. + /// The exit code. private static async Task Main(string[] args) { var simulate = new Command("simulate") @@ -55,6 +66,11 @@ private static async Task Main(string[] args) return await root.InvokeAsync(args); } + /// + /// Simulates the entry point. + /// + /// The entry point adapter. + /// The simulator to use. private static async Task Simulate(@EntryPointAdapter entryPoint, SimulatorKind simulator) { var result = await WithSimulator(entryPoint.Run, simulator); @@ -66,6 +82,10 @@ private static async Task Simulate(@EntryPointAdapter entryPoint, SimulatorKind } } + /// + /// Estimates the resource usage of the Q# program. + /// + /// The entry point adapter. private static async Task Resources(@EntryPointAdapter entryPoint) { var estimator = new ResourcesEstimator(); @@ -73,6 +93,13 @@ private static async Task Resources(@EntryPointAdapter entryPoint) Console.Write(estimator.ToTSV()); } + /// + /// Simulates a callable. + /// + /// The return type of the callable. + /// The callable to simulate. + /// The simulator to use. + /// The return value of the callable. private static async Task WithSimulator(Func> callable, SimulatorKind simulator) { switch (simulator) @@ -89,6 +116,13 @@ private static async Task WithSimulator(Func> c } } + /// + /// 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 full 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 @@ -98,12 +132,21 @@ private static @Result ParseRangeFromEnumerable(string option, string ar : @Result.Failure( $"Cannot parse argument '{arg}' for option '{option}' as expected type {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( $"Cannot parse argument '{str}' for option '{option}' as expected type {typeof(long)}."); + /// + /// The names of simulators that can be used to simulate the entry point. + /// private enum SimulatorKind { FullState, @@ -111,6 +154,10 @@ private enum SimulatorKind } } + /// + /// The result of a process that can either succeed or fail. + /// + /// The type of the result value. internal struct @Result { public bool IsSuccess { get; } @@ -130,16 +177,53 @@ private @Result(bool isSuccess, T value, string errorMessage) public static @Result Failure(string errorMessage) => new @Result(false, default, errorMessage); } + /// + /// Extensions method 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 value type of the first result. + /// The value type of the second result. + /// 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 binder function on + /// the first result's value. + /// internal static @Result Bind(this @Result result, Func> binder) => result.IsFailure ? @Result.Failure(result.ErrorMessage) : binder(result.Value); + /// + /// Converts an enumerable of results into a result of an enumerable. + /// + /// The value type of the results. + /// 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<@Result> results) => results.All(result => result.IsSuccess) ? @Result>.Success(results.Select(results => results.Value)) : @Result>.Failure(results.First(results => results.IsFailure).ErrorMessage); + /// + /// Chooses the first successful result out of an enumerable of results. + /// + /// The type of the result values. + /// The results to choose from. + /// + /// The action to call with an enumerable of error messages if all of the results are failures. + /// + /// + /// The value of the first successful result, or default if none of the results were successful. + /// internal static T Choose(this IEnumerable<@Result> results, Action> onError) { if (results.Any(result => result.IsSuccess)) From f356c8a602568c5d8a3ebbedc2df5c98fd00cb61 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 14 Apr 2020 01:33:17 -0700 Subject: [PATCH 20/70] Add argument handler for BigInt --- src/Simulation/CsharpGeneration/EntryPoint.fs | 7 ++---- .../Resources/EntryPointDriver.cs | 25 +++++++++++++++++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 276f2a8b3b7..62c3a174bd7 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -29,9 +29,6 @@ let private resultStructName = "__QsResult__" /// The name of the class containing extension methods for the result struct. let private resultExtensionsClassName = "__QsResultExtensions__" -/// The name of the property containing the argument handler for ranges. -let private rangeHandlerPropertyName = "RangeArgumentHandler" - /// Returns a sequence of all of the named parameters in the argument tuple and their respective C# and Q# types. let rec private getParameters context doc = function | QsTupleItem variable -> @@ -54,7 +51,8 @@ let rec private getParameters context doc = function /// Returns the custom argument handler for the given Q# type. let private getArgumentHandler = function - | Range -> ``ident`` driverClassName <|.|> ``ident`` rangeHandlerPropertyName |> Some + | BigInt -> ``ident`` driverClassName <|.|> ``ident`` "BigIntArgumentHandler" |> Some + | Range -> ``ident`` driverClassName <|.|> ``ident`` "RangeArgumentHandler" |> Some | _ -> None /// Returns a property containing a sequence of command-line options corresponding to each parameter given. @@ -161,7 +159,6 @@ let private getDriver (entryPoint : QsCallable) = .Replace("@Namespace", entryPoint.FullName.Namespace.Value) .Replace("@EntryPointDriver", "__QsEntryPointDriver__") .Replace("@EntryPointAdapter", adapterClassName) - .Replace("@RangeArgumentHandler", rangeHandlerPropertyName) .Replace("@ResultExtensions", resultExtensionsClassName) .Replace("@Result", resultStructName) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index 059e2fb46c8..4615ee2b6e4 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -8,6 +8,7 @@ using System.CommandLine.Invocation; using System.CommandLine.Parsing; using System.Linq; + using System.Numerics; using System.Threading.Tasks; /// @@ -16,9 +17,29 @@ internal static class @EntryPointDriver { /// - /// The argument handler for the Q# range type. + /// The argument handler for the Q# BigInt type. /// - internal static readonly Argument @RangeArgumentHandler = + internal static readonly Argument BigIntArgumentHandler = + new Argument(result => + { + var option = (result.Parent as OptionResult)?.Token.Value ?? result.Argument.Name; + var arg = result.Tokens.Single().Value; + if (BigInteger.TryParse(arg, out var num)) + { + return num; + } + else + { + result.ErrorMessage = + $"Cannot parse argument '{arg}' for option '{option}' as expected type {typeof(BigInteger)}."; + return default; + } + }); + + /// + /// The argument handler for the Q# Range type. + /// + internal static readonly Argument RangeArgumentHandler = new Argument(result => { var option = (result.Parent as OptionResult)?.Token.Value ?? result.Argument.Name; From f8545797b9370acd3d19dca672b92d56c8394b21 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 14 Apr 2020 01:35:57 -0700 Subject: [PATCH 21/70] Don't allow more dots than necessary in range --- src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index 4615ee2b6e4..29911fddeb3 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -47,7 +47,7 @@ internal static class @EntryPointDriver return new[] { ParseRangeFromEnumerable(option, arg, result.Tokens.Select(token => token.Value)), - ParseRangeFromEnumerable(option, arg, arg.Split("..", StringSplitOptions.RemoveEmptyEntries)) + ParseRangeFromEnumerable(option, arg, arg.Split("..")) } .Choose(errors => result.ErrorMessage = string.Join('\n', errors)); }) From 29798f1ed4aae19e21a85daee4568d567dbfe4ce Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 14 Apr 2020 01:40:07 -0700 Subject: [PATCH 22/70] Don't show duplicate errors --- src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index 29911fddeb3..da1616e9700 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -49,7 +49,7 @@ internal static class @EntryPointDriver ParseRangeFromEnumerable(option, arg, result.Tokens.Select(token => token.Value)), ParseRangeFromEnumerable(option, arg, arg.Split("..")) } - .Choose(errors => result.ErrorMessage = string.Join('\n', errors)); + .Choose(errors => result.ErrorMessage = string.Join('\n', errors.Distinct())); }) { Arity = ArgumentArity.OneOrMore From 12041a88350e3c2a204955abe4065ab7b810f623 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 14 Apr 2020 10:22:31 -0700 Subject: [PATCH 23/70] Add function to create error messages --- .../Resources/EntryPointDriver.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index da1616e9700..c1b290a6d98 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -30,8 +30,7 @@ internal static class @EntryPointDriver } else { - result.ErrorMessage = - $"Cannot parse argument '{arg}' for option '{option}' as expected type {typeof(BigInteger)}."; + result.ErrorMessage = GetErrorMessage(option, arg, typeof(BigInteger)); return default; } }); @@ -150,8 +149,7 @@ private static @Result ParseRangeFromEnumerable(string option, string ar ? @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( - $"Cannot parse argument '{arg}' for option '{option}' as expected type {typeof(QRange)}.")); + : @Result.Failure(GetErrorMessage(option, arg, typeof(QRange)))); /// /// Parses a long from a string. @@ -162,8 +160,17 @@ private static @Result ParseRangeFromEnumerable(string option, string ar private static @Result TryParseLong(string option, string str) => long.TryParse(str, out var result) ? @Result.Success(result) - : @Result.Failure( - $"Cannot parse argument '{str}' for option '{option}' as expected type {typeof(long)}."); + : @Result.Failure(GetErrorMessage(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. + /// + private static string GetErrorMessage(string option, string arg, Type type) => + $"Cannot parse argument '{arg}' for option '{option}' as expected type {type}."; /// /// The names of simulators that can be used to simulate the entry point. From af30876dad833611cbafc430c913fa699f441287 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 14 Apr 2020 10:59:01 -0700 Subject: [PATCH 24/70] Add argument handler for Result type --- src/Simulation/CsharpGeneration/EntryPoint.fs | 1 + .../Resources/EntryPointDriver.cs | 71 +++++++++++-------- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 62c3a174bd7..ffdc2965802 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -51,6 +51,7 @@ let rec private getParameters context doc = function /// Returns the custom argument handler for the given Q# type. let private getArgumentHandler = function + | Result -> ``ident`` driverClassName <|.|> ``ident`` "ResultArgumentHandler" |> Some | BigInt -> ``ident`` driverClassName <|.|> ``ident`` "BigIntArgumentHandler" |> Some | Range -> ``ident`` driverClassName <|.|> ``ident`` "RangeArgumentHandler" |> Some | _ -> None diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index c1b290a6d98..1f066b13979 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -17,42 +17,57 @@ internal static class @EntryPointDriver { /// - /// The argument handler for the Q# BigInt type. + /// The argument handler for the Q# Result type. /// - internal static readonly Argument BigIntArgumentHandler = - new Argument(result => + internal static readonly Argument ResultArgumentHandler = new Argument(result => + { + var arg = result.Tokens.Single().Value; + switch (arg.ToLower()) { - var option = (result.Parent as OptionResult)?.Token.Value ?? result.Argument.Name; - var arg = result.Tokens.Single().Value; - if (BigInteger.TryParse(arg, out var num)) - { - return num; - } - else - { - result.ErrorMessage = GetErrorMessage(option, arg, typeof(BigInteger)); + case "zero": return Result.Zero; + case "one": return Result.One; + default: + result.ErrorMessage = GetErrorMessage( + ((OptionResult)result.Parent).Token.Value, arg, typeof(Result)); return default; - } - }); + }; + }); /// - /// The argument handler for the Q# Range type. + /// The argument handler for the Q# BigInt type. /// - internal static readonly Argument RangeArgumentHandler = - new Argument(result => + internal static readonly Argument BigIntArgumentHandler = new Argument(result => + { + var arg = result.Tokens.Single().Value; + if (BigInteger.TryParse(arg, out var num)) { - var option = (result.Parent as OptionResult)?.Token.Value ?? result.Argument.Name; - var arg = string.Join(' ', result.Tokens.Select(token => token.Value)); - return new[] - { - ParseRangeFromEnumerable(option, arg, result.Tokens.Select(token => token.Value)), - ParseRangeFromEnumerable(option, arg, arg.Split("..")) - } - .Choose(errors => result.ErrorMessage = string.Join('\n', errors.Distinct())); - }) + return num; + } + else { - Arity = ArgumentArity.OneOrMore - }; + result.ErrorMessage = GetErrorMessage( + ((OptionResult)result.Parent).Token.Value, arg, typeof(BigInteger)); + return default; + } + }); + + /// + /// The argument handler for the Q# Range type. + /// + internal static readonly Argument RangeArgumentHandler = new Argument(result => + { + var option = ((OptionResult)result.Parent).Token.Value; + var arg = string.Join(' ', result.Tokens.Select(token => token.Value)); + return new[] + { + ParseRangeFromEnumerable(option, arg, result.Tokens.Select(token => token.Value)), + ParseRangeFromEnumerable(option, arg, arg.Split("..")) + } + .Choose(errors => result.ErrorMessage = string.Join('\n', errors.Distinct())); + }) + { + Arity = ArgumentArity.OneOrMore + }; /// /// Runs the entry point. From b768e31586a0987c72501b29f84ef84901fb98bd Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 14 Apr 2020 11:06:18 -0700 Subject: [PATCH 25/70] Add argument handler for type Unit --- src/Simulation/CsharpGeneration/EntryPoint.fs | 1 + .../Resources/EntryPointDriver.cs | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index ffdc2965802..31237b864fc 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -51,6 +51,7 @@ let rec private getParameters context doc = function /// Returns the custom argument handler for the given Q# type. let private getArgumentHandler = function + | UnitType -> ``ident`` driverClassName <|.|> ``ident`` "UnitArgumentHandler" |> Some | Result -> ``ident`` driverClassName <|.|> ``ident`` "ResultArgumentHandler" |> Some | BigInt -> ``ident`` driverClassName <|.|> ``ident`` "BigIntArgumentHandler" |> Some | Range -> ``ident`` driverClassName <|.|> ``ident`` "RangeArgumentHandler" |> Some diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index 1f066b13979..3bdd4004570 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -16,12 +16,25 @@ /// internal static class @EntryPointDriver { + internal static readonly Argument UnitArgumentHandler = new Argument(result => + { + if (result.Tokens.Single().Value.Trim() == "()") + { + return QVoid.Instance; + } + else + { + result.ErrorMessage = GetErrorMessage(((OptionResult)result.Parent).Token.Value, arg, typeof(Result)); + return default; + } + }); + /// /// The argument handler for the Q# Result type. /// internal static readonly Argument ResultArgumentHandler = new Argument(result => { - var arg = result.Tokens.Single().Value; + var arg = result.Tokens.Single().Value.Trim(); switch (arg.ToLower()) { case "zero": return Result.Zero; From 091ce8320318fa69cc9ddfef123ad5a8ea9e0ca4 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 14 Apr 2020 11:17:18 -0700 Subject: [PATCH 26/70] Change readonly fields to properties --- .../CsharpGeneration/Resources/EntryPointDriver.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index 3bdd4004570..012a61813d4 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -16,15 +16,16 @@ /// internal static class @EntryPointDriver { - internal static readonly Argument UnitArgumentHandler = new Argument(result => + internal static Argument UnitArgumentHandler { get; } = new Argument(result => { - if (result.Tokens.Single().Value.Trim() == "()") + var arg = result.Tokens.Single().Value; + if (arg.Trim() == "()") { return QVoid.Instance; } else { - result.ErrorMessage = GetErrorMessage(((OptionResult)result.Parent).Token.Value, arg, typeof(Result)); + result.ErrorMessage = GetErrorMessage(((OptionResult)result.Parent).Token.Value, arg, typeof(QVoid)); return default; } }); @@ -32,7 +33,7 @@ internal static class @EntryPointDriver /// /// The argument handler for the Q# Result type. /// - internal static readonly Argument ResultArgumentHandler = new Argument(result => + internal static Argument ResultArgumentHandler { get; } = new Argument(result => { var arg = result.Tokens.Single().Value.Trim(); switch (arg.ToLower()) @@ -49,7 +50,7 @@ internal static class @EntryPointDriver /// /// The argument handler for the Q# BigInt type. /// - internal static readonly Argument BigIntArgumentHandler = new Argument(result => + internal static Argument BigIntArgumentHandler { get; } = new Argument(result => { var arg = result.Tokens.Single().Value; if (BigInteger.TryParse(arg, out var num)) @@ -67,7 +68,7 @@ internal static class @EntryPointDriver /// /// The argument handler for the Q# Range type. /// - internal static readonly Argument RangeArgumentHandler = new Argument(result => + internal static Argument RangeArgumentHandler { get; } = new Argument(result => { var option = ((OptionResult)result.Parent).Token.Value; var arg = string.Join(' ', result.Tokens.Select(token => token.Value)); From 1fa663d62caeb4acd3fb6a4297ced562b8021103 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 14 Apr 2020 12:52:18 -0700 Subject: [PATCH 27/70] Refactor argument parsers --- .../Resources/EntryPointDriver.cs | 72 +++++++++---------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index 012a61813d4..78ecdb90846 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -16,54 +16,28 @@ /// internal static class @EntryPointDriver { - internal static Argument UnitArgumentHandler { get; } = new Argument(result => - { - var arg = result.Tokens.Single().Value; - if (arg.Trim() == "()") - { - return QVoid.Instance; - } - else - { - result.ErrorMessage = GetErrorMessage(((OptionResult)result.Parent).Token.Value, arg, typeof(QVoid)); - return default; - } - }); + /// + /// The argument handler for the Q# Unit type. + /// + internal static Argument UnitArgumentHandler { get; } = new Argument( + CreateArgumentParser(arg => arg.Trim() == "()" ? (true, QVoid.Instance) : (false, default))); /// /// The argument handler for the Q# Result type. /// - internal static Argument ResultArgumentHandler { get; } = new Argument(result => - { - var arg = result.Tokens.Single().Value.Trim(); - switch (arg.ToLower()) + internal static Argument ResultArgumentHandler { get; } = new Argument( + CreateArgumentParser(arg => arg.Trim().ToLower() switch { - case "zero": return Result.Zero; - case "one": return Result.One; - default: - result.ErrorMessage = GetErrorMessage( - ((OptionResult)result.Parent).Token.Value, arg, typeof(Result)); - return default; - }; - }); + "zero" => (true, Result.Zero), + "one" => (true, Result.One), + _ => (false, default) + })); /// /// The argument handler for the Q# BigInt type. /// - internal static Argument BigIntArgumentHandler { get; } = new Argument(result => - { - var arg = result.Tokens.Single().Value; - if (BigInteger.TryParse(arg, out var num)) - { - return num; - } - else - { - result.ErrorMessage = GetErrorMessage( - ((OptionResult)result.Parent).Token.Value, arg, typeof(BigInteger)); - return default; - } - }); + internal static Argument BigIntArgumentHandler { get; } = new Argument( + CreateArgumentParser(arg => BigInteger.TryParse(arg, out var result) ? (true, result) : (false, default))); /// /// The argument handler for the Q# Range type. @@ -165,6 +139,26 @@ private static async Task WithSimulator(Func> c } } + /// + /// 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 parser) => result => + { + var (success, value) = parser(result.Tokens.Single().Value); + if (!success) + { + result.ErrorMessage = GetErrorMessage( + ((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. /// From c77cf9fe6682c4e0022e48d22d43f49152c94cac Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 14 Apr 2020 13:07:47 -0700 Subject: [PATCH 28/70] Refactor getArgumentHandler --- src/Simulation/CsharpGeneration/EntryPoint.fs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 31237b864fc..88cbf5d83a1 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -50,12 +50,14 @@ let rec private getParameters context doc = function | QsTuple items -> items |> Seq.map (getParameters context doc) |> Seq.concat /// Returns the custom argument handler for the given Q# type. -let private getArgumentHandler = function - | UnitType -> ``ident`` driverClassName <|.|> ``ident`` "UnitArgumentHandler" |> Some - | Result -> ``ident`` driverClassName <|.|> ``ident`` "ResultArgumentHandler" |> Some - | BigInt -> ``ident`` driverClassName <|.|> ``ident`` "BigIntArgumentHandler" |> Some - | Range -> ``ident`` driverClassName <|.|> ``ident`` "RangeArgumentHandler" |> Some +let private getArgumentHandler = + function + | UnitType -> Some "UnitArgumentHandler" + | Result -> Some "ResultArgumentHandler" + | BigInt -> Some "BigIntArgumentHandler" + | Range -> Some "RangeArgumentHandler" | _ -> None + >> Option.map (fun handler -> ``ident`` driverClassName <|.|> ``ident`` handler) /// Returns a property containing a sequence of command-line options corresponding to each parameter given. let private getParameterOptionsProperty parameters = From f30a39784e8d6a23117ab8243b09f964a9056adc Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 14 Apr 2020 16:16:10 -0700 Subject: [PATCH 29/70] Remove extra blank line --- src/Simulation/CsharpGeneration/SimulationCode.fs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Simulation/CsharpGeneration/SimulationCode.fs b/src/Simulation/CsharpGeneration/SimulationCode.fs index c909730f959..f13c59c4973 100644 --- a/src/Simulation/CsharpGeneration/SimulationCode.fs +++ b/src/Simulation/CsharpGeneration/SimulationCode.fs @@ -1593,7 +1593,6 @@ module SimulationCode = let attributes = localElements |> List.map (snd >> buildDeclarationAttributes) |> List.concat let namespaces = localElements |> List.map (buildNamespace context) - ``compilation unit`` attributes usings From 0925f7638477b6a2496f20ebbb557594c105a874 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 15 Apr 2020 11:00:24 -0700 Subject: [PATCH 30/70] Use Enum.TryParse for Result --- .../CsharpGeneration/Resources/EntryPointDriver.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index 78ecdb90846..f0b05f59c4b 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -26,12 +26,12 @@ internal static class @EntryPointDriver /// The argument handler for the Q# Result type. /// internal static Argument ResultArgumentHandler { get; } = new Argument( - CreateArgumentParser(arg => arg.Trim().ToLower() switch + CreateArgumentParser(arg => Enum.TryParse(arg, ignoreCase: true, out ResultValue result) ? result switch { - "zero" => (true, Result.Zero), - "one" => (true, Result.One), + ResultValue.Zero => (true, Result.Zero), + ResultValue.One => (true, Result.One), _ => (false, default) - })); + } : (false, default))); /// /// The argument handler for the Q# BigInt type. From 261eb2db7b82954cdb4543258f557f6926f7d302 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 15 Apr 2020 11:37:13 -0700 Subject: [PATCH 31/70] Combine simulate and resources commands --- .../Resources/EntryPointDriver.cs | 62 +++++++------------ 1 file changed, 22 insertions(+), 40 deletions(-) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index f0b05f59c4b..d8a73495992 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -71,16 +71,10 @@ private static async Task Main(string[] args) }; simulate.AddOption(new Option( new[] { "--simulator", "-s" }, - () => SimulatorKind.FullState, + () => SimulatorKind.QuantumSimulator, "The name of the simulator to use.")); - var resources = new Command("resources") - { - Description = "Estimate the resource usage of the program.", - Handler = CommandHandler.Create<@EntryPointAdapter>(Resources) - }; - - var root = new RootCommand(@EntryPointAdapter.Summary) { simulate, resources }; + var root = new RootCommand(@EntryPointAdapter.Summary) { simulate }; foreach (var option in @EntryPointAdapter.Options) { root.AddGlobalOption(option); @@ -96,44 +90,31 @@ private static async Task Main(string[] args) /// The simulator to use. private static async Task Simulate(@EntryPointAdapter entryPoint, SimulatorKind simulator) { - var result = await WithSimulator(entryPoint.Run, simulator); -#pragma warning disable CS0184 - if (!(result is QVoid)) -#pragma warning restore CS0184 + static void DisplayReturnValue(T value) { - Console.WriteLine(result); + if (!(value is QVoid)) + { + Console.WriteLine(value); + } } - } - - /// - /// Estimates the resource usage of the Q# program. - /// - /// The entry point adapter. - private static async Task Resources(@EntryPointAdapter entryPoint) - { - var estimator = new ResourcesEstimator(); - await entryPoint.Run(estimator); - Console.Write(estimator.ToTSV()); - } - /// - /// Simulates a callable. - /// - /// The return type of the callable. - /// The callable to simulate. - /// The simulator to use. - /// The return value of the callable. - private static async Task WithSimulator(Func> callable, SimulatorKind simulator) - { + // TODO: Support custom simulators. switch (simulator) { - case SimulatorKind.FullState: + case SimulatorKind.QuantumSimulator: using (var quantumSimulator = new QuantumSimulator()) { - return await callable(quantumSimulator); + DisplayReturnValue(await entryPoint.Run(quantumSimulator)); } - case SimulatorKind.Toffoli: - return await callable(new ToffoliSimulator()); + break; + case SimulatorKind.ToffoliSimulator: + DisplayReturnValue(await entryPoint.Run(new ToffoliSimulator())); + break; + case SimulatorKind.ResourcesEstimator: + var resourcesEstimator = new ResourcesEstimator(); + await entryPoint.Run(resourcesEstimator); + Console.WriteLine(resourcesEstimator.ToTSV()); + break; default: throw new ArgumentException("Invalid simulator."); } @@ -200,8 +181,9 @@ private static string GetErrorMessage(string option, string arg, Type type) => /// private enum SimulatorKind { - FullState, - Toffoli + QuantumSimulator, + ToffoliSimulator, + ResourcesEstimator } } From b0e2044ab0679993627b07cdb59485bbf5125dea Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 15 Apr 2020 11:50:24 -0700 Subject: [PATCH 32/70] Pass simulate description to constructor --- .../CsharpGeneration/Resources/EntryPointDriver.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index d8a73495992..9b245a3ea6f 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -64,15 +64,13 @@ internal static class @EntryPointDriver /// The exit code. private static async Task Main(string[] args) { - var simulate = new Command("simulate") + var simulate = new Command("simulate", "Run the program using a local simulator.") { - Description = "Run the program using a local simulator.", - Handler = CommandHandler.Create<@EntryPointAdapter, SimulatorKind>(Simulate) + new Option(new[] { "--simulator", "-s" }, + () => SimulatorKind.QuantumSimulator, + "The name of the simulator to use.") }; - simulate.AddOption(new Option( - new[] { "--simulator", "-s" }, - () => SimulatorKind.QuantumSimulator, - "The name of the simulator to use.")); + simulate.Handler = CommandHandler.Create<@EntryPointAdapter, SimulatorKind>(Simulate); var root = new RootCommand(@EntryPointAdapter.Summary) { simulate }; foreach (var option in @EntryPointAdapter.Options) From 1a63d62994a97ffd6f076c5f2d0d6048a9acd4d3 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 15 Apr 2020 12:05:05 -0700 Subject: [PATCH 33/70] Set simulate command as the default --- .../Resources/EntryPointDriver.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index 9b245a3ea6f..d8356ac6ede 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -64,7 +64,7 @@ internal static class @EntryPointDriver /// The exit code. private static async Task Main(string[] args) { - var simulate = new Command("simulate", "Run the program using a local simulator.") + var simulate = new Command("simulate", "(default) Run the program using a local simulator.") { new Option(new[] { "--simulator", "-s" }, () => SimulatorKind.QuantumSimulator, @@ -73,11 +73,12 @@ private static async Task Main(string[] args) simulate.Handler = CommandHandler.Create<@EntryPointAdapter, SimulatorKind>(Simulate); var root = new RootCommand(@EntryPointAdapter.Summary) { simulate }; - foreach (var option in @EntryPointAdapter.Options) - { - root.AddGlobalOption(option); - } - root.AddValidator(result => "A command is required."); + foreach (var option in @EntryPointAdapter.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 root.InvokeAsync(args); } @@ -90,10 +91,7 @@ private static async Task Simulate(@EntryPointAdapter entryPoint, SimulatorKind { static void DisplayReturnValue(T value) { - if (!(value is QVoid)) - { - Console.WriteLine(value); - } + if (!(value is QVoid)) { Console.WriteLine(value); } } // TODO: Support custom simulators. From e106ace0540a59ae2029951901a5f3116b696357 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 15 Apr 2020 12:26:52 -0700 Subject: [PATCH 34/70] Add suggestions for custom argument types --- .../Resources/EntryPointDriver.cs | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index d8356ac6ede..16681172367 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -19,37 +19,54 @@ internal static class @EntryPointDriver /// /// The argument handler for the Q# Unit type. /// - internal static Argument UnitArgumentHandler { get; } = new Argument( - CreateArgumentParser(arg => arg.Trim() == "()" ? (true, QVoid.Instance) : (false, default))); + 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; } = new Argument( - CreateArgumentParser(arg => Enum.TryParse(arg, ignoreCase: true, out ResultValue result) ? result switch + internal static Argument ResultArgumentHandler + { + get { - ResultValue.Zero => (true, Result.Zero), - ResultValue.One => (true, Result.One), - _ => (false, default) - } : (false, default))); + 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 { get; } = new Argument( - CreateArgumentParser(arg => BigInteger.TryParse(arg, out var result) ? (true, result) : (false, default))); + 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 { get; } = new Argument(result => + internal static Argument RangeArgumentHandler => new Argument(result => { var option = ((OptionResult)result.Parent).Token.Value; - var arg = string.Join(' ', result.Tokens.Select(token => token.Value)); + var value = string.Join(' ', result.Tokens.Select(token => token.Value)); return new[] { - ParseRangeFromEnumerable(option, arg, result.Tokens.Select(token => token.Value)), - ParseRangeFromEnumerable(option, arg, arg.Split("..")) + ParseRangeFromEnumerable(option, value, result.Tokens.Select(token => token.Value)), + ParseRangeFromEnumerable(option, value, value.Split("..")) } .Choose(errors => result.ErrorMessage = string.Join('\n', errors.Distinct())); }) @@ -140,7 +157,7 @@ private static ParseArgument CreateArgumentParser(Func /// 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 full argument string for the option. + /// 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) => From 59175c09cc002fe55f2aa7fa2443c9287b9adfc8 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 15 Apr 2020 15:47:07 -0700 Subject: [PATCH 35/70] Entry point option names shadow standard names --- .../Resources/EntryPointDriver.cs | 85 +++++++++++++------ 1 file changed, 60 insertions(+), 25 deletions(-) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index 16681172367..46d63902931 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -81,12 +81,11 @@ internal static Argument ResultArgumentHandler /// The exit code. private static async Task Main(string[] args) { - var simulate = new Command("simulate", "(default) Run the program using a local simulator.") - { - new Option(new[] { "--simulator", "-s" }, - () => SimulatorKind.QuantumSimulator, - "The name of the simulator to use.") - }; + var simulate = new Command("simulate", "(default) Run the program using a local simulator."); + TryCreateOption(new[] { "--simulator", "-s" }, + () => SimulatorKind.QuantumSimulator, + "The name of the simulator to use.") + .Then(simulate.AddOption); simulate.Handler = CommandHandler.Create<@EntryPointAdapter, SimulatorKind>(Simulate); var root = new RootCommand(@EntryPointAdapter.Summary) { simulate }; @@ -106,9 +105,12 @@ private static async Task Main(string[] args) /// The simulator to use. private static async Task Simulate(@EntryPointAdapter entryPoint, SimulatorKind simulator) { - static void DisplayReturnValue(T value) + static void displayReturnValue(T value) { - if (!(value is QVoid)) { Console.WriteLine(value); } + if (!(value is QVoid)) + { + Console.WriteLine(value); + } } // TODO: Support custom simulators. @@ -117,11 +119,11 @@ static void DisplayReturnValue(T value) case SimulatorKind.QuantumSimulator: using (var quantumSimulator = new QuantumSimulator()) { - DisplayReturnValue(await entryPoint.Run(quantumSimulator)); + displayReturnValue(await entryPoint.Run(quantumSimulator)); } break; case SimulatorKind.ToffoliSimulator: - DisplayReturnValue(await entryPoint.Run(new ToffoliSimulator())); + displayReturnValue(await entryPoint.Run(new ToffoliSimulator())); break; case SimulatorKind.ResourcesEstimator: var resourcesEstimator = new ResourcesEstimator(); @@ -133,18 +135,39 @@ static void DisplayReturnValue(T value) } } + /// + /// Tries to create an option by ignoring aliases that are already in use by the entry point. If all of the + /// aliases are in use, 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. + /// + private static @Result> TryCreateOption( + IEnumerable aliases, Func getDefaultValue, string description = null) + { + static bool isAliasAvailable(string alias) => + !@EntryPointAdapter.Options.SelectMany(option => option.RawAliases).Contains(alias); + + var validAliases = aliases.Where(isAliasAvailable); + return validAliases.Any() + ? @Result>.Success(new Option(validAliases.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 parser) => result => + private static ParseArgument CreateArgumentParser(Func parse) => result => { - var (success, value) = parser(result.Tokens.Single().Value); + var (success, value) = parse(result.Tokens.Single().Value); if (!success) { result.ErrorMessage = GetErrorMessage( @@ -220,11 +243,11 @@ private @Result(bool isSuccess, T value, string errorMessage) public static @Result Success(T value) => new @Result(true, value, default); - public static @Result Failure(string errorMessage) => new @Result(false, default, errorMessage); + public static @Result Failure(string errorMessage = null) => new @Result(false, default, errorMessage); } /// - /// Extensions method for . + /// Extensions method for . /// internal static class @ResultExtensions { @@ -232,23 +255,21 @@ 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 value type of the first result. - /// The value type of the second result. + /// 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. - /// + /// 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 binder function on - /// the first result's value. + /// 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> binder) => - result.IsFailure ? @Result.Failure(result.ErrorMessage) : binder(result.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 value type of the results. + /// 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 @@ -259,6 +280,20 @@ internal static @Result> Sequence(this IEnumerable<@Result> ? @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); + } + } + /// /// Chooses the first successful result out of an enumerable of results. /// From 39997c694b343c77093924c734e438441e001edf Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 15 Apr 2020 17:35:40 -0700 Subject: [PATCH 36/70] Support custom simulators --- .../Resources/EntryPointDriver.cs | 96 +++++++++++++------ 1 file changed, 66 insertions(+), 30 deletions(-) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index 46d63902931..af135e69398 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -82,11 +82,10 @@ internal static Argument ResultArgumentHandler private static async Task Main(string[] args) { var simulate = new Command("simulate", "(default) Run the program using a local simulator."); - TryCreateOption(new[] { "--simulator", "-s" }, - () => SimulatorKind.QuantumSimulator, - "The name of the simulator to use.") + TryCreateOption( + new[] { "--simulator", "-s" }, () => "QuantumSimulator", "The name of the simulator to use.") .Then(simulate.AddOption); - simulate.Handler = CommandHandler.Create<@EntryPointAdapter, SimulatorKind>(Simulate); + simulate.Handler = CommandHandler.Create<@EntryPointAdapter, string>(Simulate); var root = new RootCommand(@EntryPointAdapter.Summary) { simulate }; foreach (var option in @EntryPointAdapter.Options) { root.AddGlobalOption(option); } @@ -103,35 +102,82 @@ private static async Task Main(string[] args) /// /// The entry point adapter. /// The simulator to use. - private static async Task Simulate(@EntryPointAdapter entryPoint, SimulatorKind simulator) + /// The exit code. + private static async Task Simulate(@EntryPointAdapter entryPoint, string simulator) { - static void displayReturnValue(T value) - { - if (!(value is QVoid)) - { - Console.WriteLine(value); - } - } - - // TODO: Support custom simulators. switch (simulator) { - case SimulatorKind.QuantumSimulator: + case "QuantumSimulator": using (var quantumSimulator = new QuantumSimulator()) { - displayReturnValue(await entryPoint.Run(quantumSimulator)); + DisplayReturnValue(await entryPoint.Run(quantumSimulator)); } break; - case SimulatorKind.ToffoliSimulator: - displayReturnValue(await entryPoint.Run(new ToffoliSimulator())); + case "ToffoliSimulator": + DisplayReturnValue(await entryPoint.Run(new ToffoliSimulator())); break; - case SimulatorKind.ResourcesEstimator: + case "ResourcesEstimator": var resourcesEstimator = new ResourcesEstimator(); await entryPoint.Run(resourcesEstimator); Console.WriteLine(resourcesEstimator.ToTSV()); break; default: - throw new ArgumentException("Invalid simulator."); + return await RunCustomSimulator(entryPoint, simulator); + } + return 0; + } + + /// + /// Simulates the entry point using a custom simulator, given by a fully-qualified name if it is in the current + /// assembly, and an assembly-qualified name otherwise. + /// + /// The entry point adapter. + /// The fully-qualified or assembly-qualified name of the simulator to use. + /// The exit code. + private static async Task RunCustomSimulator(@EntryPointAdapter entryPoint, string name) + { + var originalColor = Console.ForegroundColor; + IOperationFactory simulator; + try + { + simulator = (IOperationFactory)Activator.CreateInstance(Type.GetType(name, true)); + } + catch (Exception exception) + { + Console.Error.WriteLine($"The simulator '{name}' could not be used:"); + Console.ForegroundColor = ConsoleColor.Red; + Console.Error.WriteLine($" {exception.GetType()}: {exception.Message}"); + return 1; + } + finally + { + Console.ForegroundColor = originalColor; + } + + try + { + DisplayReturnValue(await entryPoint.Run(simulator)); + } + finally + { + if (simulator is IDisposable disposable) + { + disposable.Dispose(); + } + } + return 0; + } + + /// + /// Writes the return value of the entry point to the console. + /// + /// The type of the return value. + /// The return value. + private static void DisplayReturnValue(T value) + { + if (!(value is QVoid)) + { + Console.WriteLine(value); } } @@ -211,16 +257,6 @@ private static @Result TryParseLong(string option, string str) => /// private static string GetErrorMessage(string option, string arg, Type type) => $"Cannot parse argument '{arg}' for option '{option}' as expected type {type}."; - - /// - /// The names of simulators that can be used to simulate the entry point. - /// - private enum SimulatorKind - { - QuantumSimulator, - ToffoliSimulator, - ResourcesEstimator - } } /// From f0a6b5155f2d690bf24fdced094ab9472acb7359 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Thu, 16 Apr 2020 11:29:39 -0700 Subject: [PATCH 37/70] Use DefaultSimulator property --- src/Simulation/CsharpGeneration/EntryPoint.fs | 19 ++++++++++++++----- .../Resources/EntryPointDriver.cs | 5 +++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 88cbf5d83a1..af7da5a60f0 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -122,14 +122,23 @@ let private getRunMethod context (entryPoint : QsCallable) = /// Returns the class that adapts the entry point for use with the command-line parsing library and the driver. let private getAdapterClass context (entryPoint : QsCallable) = - let summary = - ``property-arrow_get`` "string" "Summary" [``public``; ``static``] - ``get`` (``=>`` (``literal`` ((PrintSummary entryPoint.Documentation false).Trim ()))) + let constant name typeName value = + ``property-arrow_get`` typeName name [``public``; ``static``] + ``get`` (``=>`` (``literal`` value)) + let summary = constant "Summary" "string" ((PrintSummary entryPoint.Documentation false).Trim ()) + let defaultSimulator = + context.assemblyConstants.TryGetValue "DefaultSimulator" + |> snd + |> (fun value -> if String.IsNullOrWhiteSpace value then "QuantumSimulator" else value) + |> constant "DefaultSimulator" "string" let parameters = getParameters context entryPoint.Documentation entryPoint.ArgumentTuple let members : seq = Seq.concat [ - Seq.singleton (upcast summary) - Seq.singleton (upcast getParameterOptionsProperty parameters) + Seq.ofList [ + summary + defaultSimulator + getParameterOptionsProperty parameters + ] getParameterProperties parameters |> Seq.map (fun property -> upcast property) Seq.singleton (upcast getRunMethod context entryPoint) ] diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index af135e69398..f3af6d65016 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -82,8 +82,9 @@ internal static Argument ResultArgumentHandler private static async Task Main(string[] args) { var simulate = new Command("simulate", "(default) Run the program using a local simulator."); - TryCreateOption( - new[] { "--simulator", "-s" }, () => "QuantumSimulator", "The name of the simulator to use.") + TryCreateOption(new[] { "--simulator", "-s" }, + () => @EntryPointAdapter.DefaultSimulator, + "The name of the simulator to use.") .Then(simulate.AddOption); simulate.Handler = CommandHandler.Create<@EntryPointAdapter, string>(Simulate); From 583168faf397374bc9dc313b0a3f13839e2090dc Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Thu, 16 Apr 2020 14:41:51 -0700 Subject: [PATCH 38/70] Support custom DefaultSimulator; remove reflection --- src/Simulation/CsharpGeneration/EntryPoint.fs | 31 ++++-- .../Resources/EntryPointDriver.cs | 100 +++++++++--------- 2 files changed, 72 insertions(+), 59 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index af7da5a60f0..a6a37699251 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -1,5 +1,6 @@ module internal Microsoft.Quantum.QsCompiler.CsharpGeneration.EntryPoint +open Microsoft.CodeAnalysis.CSharp open Microsoft.CodeAnalysis.CSharp.Syntax open Microsoft.Quantum.QsCompiler.SyntaxProcessing.SyntaxExtensions open Microsoft.Quantum.QsCompiler.SyntaxTokens @@ -120,27 +121,43 @@ let private getRunMethod context (entryPoint : QsCallable) = [``public``; ``async``] (Some (``=>`` (``await`` (``ident`` entryPointName <.> (``ident`` "Run", callArgs))))) +/// Returns a method that creates an instance of the default simulator if it is a custom simulator. +let private getCustomSimulatorFactory name = + let expr : ExpressionSyntax = + match name with + | "QuantumSimulator" | "ToffoliSimulator" | "ResourcesEstimator" -> + upcast SyntaxFactory.ThrowExpression (``new`` (``type`` "InvalidOperationException") ``(`` [] ``)``) + | _ -> ``new`` (``type`` name) ``(`` [] ``)`` + ``arrow_method`` "IOperationFactory" "CreateDefaultCustomSimulator" ``<<`` [] ``>>`` + ``(`` [] ``)`` + [``public``; ``static``] + (Some (``=>`` expr)) + /// Returns the class that adapts the entry point for use with the command-line parsing library and the driver. let private getAdapterClass context (entryPoint : QsCallable) = - let constant name typeName value = + let makeProperty name typeName value = ``property-arrow_get`` typeName name [``public``; ``static``] - ``get`` (``=>`` (``literal`` value)) - let summary = constant "Summary" "string" ((PrintSummary entryPoint.Documentation false).Trim ()) + ``get`` (``=>`` value) + + let summaryProperty = + makeProperty "Summary" "string" (``literal`` ((PrintSummary entryPoint.Documentation false).Trim ())) let defaultSimulator = context.assemblyConstants.TryGetValue "DefaultSimulator" |> snd |> (fun value -> if String.IsNullOrWhiteSpace value then "QuantumSimulator" else value) - |> constant "DefaultSimulator" "string" + let defaultSimulatorProperty = makeProperty "DefaultSimulator" "string" (``literal`` defaultSimulator) let parameters = getParameters context entryPoint.Documentation entryPoint.ArgumentTuple + let members : seq = Seq.concat [ Seq.ofList [ - summary - defaultSimulator + summaryProperty + defaultSimulatorProperty getParameterOptionsProperty parameters + getCustomSimulatorFactory defaultSimulator + getRunMethod context entryPoint ] getParameterProperties parameters |> Seq.map (fun property -> upcast property) - Seq.singleton (upcast getRunMethod context entryPoint) ] ``class`` adapterClassName ``<<`` [] ``>>`` diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index f3af6d65016..8c3bc57fbd9 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -108,56 +108,47 @@ private static async Task Simulate(@EntryPointAdapter entryPoint, string si { switch (simulator) { - case "QuantumSimulator": - using (var quantumSimulator = new QuantumSimulator()) - { - DisplayReturnValue(await entryPoint.Run(quantumSimulator)); - } - break; - case "ToffoliSimulator": - DisplayReturnValue(await entryPoint.Run(new ToffoliSimulator())); - break; case "ResourcesEstimator": var resourcesEstimator = new ResourcesEstimator(); await entryPoint.Run(resourcesEstimator); Console.WriteLine(resourcesEstimator.ToTSV()); break; default: - return await RunCustomSimulator(entryPoint, simulator); + var (isCustom, createSimulator) = simulator switch + { + "QuantumSimulator" => (false, new Func(() => new QuantumSimulator())), + "ToffoliSimulator" => (false, new Func(() => new ToffoliSimulator())), + _ => (true, @EntryPointAdapter.CreateDefaultCustomSimulator) + }; + if (isCustom && simulator != @EntryPointAdapter.DefaultSimulator) + { + DisplayCustomSimulatorError(simulator); + return 1; + } + await DisplayEntryPointResult(entryPoint, createSimulator); + break; } return 0; } /// - /// Simulates the entry point using a custom simulator, given by a fully-qualified name if it is in the current - /// assembly, and an assembly-qualified name otherwise. + /// Runs the entry point on a simulator and displays its return value. /// /// The entry point adapter. - /// The fully-qualified or assembly-qualified name of the simulator to use. - /// The exit code. - private static async Task RunCustomSimulator(@EntryPointAdapter entryPoint, string name) + /// A function that creates an instance of the simulator to use. + private static async Task DisplayEntryPointResult( + @EntryPointAdapter entryPoint, Func createSimulator) { - var originalColor = Console.ForegroundColor; - IOperationFactory simulator; + var simulator = createSimulator(); try { - simulator = (IOperationFactory)Activator.CreateInstance(Type.GetType(name, true)); - } - catch (Exception exception) - { - Console.Error.WriteLine($"The simulator '{name}' could not be used:"); - Console.ForegroundColor = ConsoleColor.Red; - Console.Error.WriteLine($" {exception.GetType()}: {exception.Message}"); - return 1; - } - finally - { - Console.ForegroundColor = originalColor; - } - - try - { - DisplayReturnValue(await entryPoint.Run(simulator)); + var value = await entryPoint.Run(simulator); +#pragma warning disable CS0184 + if (!(value is QVoid)) +#pragma warning restore CS0184 + { + Console.WriteLine(value); + } } finally { @@ -166,20 +157,6 @@ private static async Task RunCustomSimulator(@EntryPointAdapter entryPoint, disposable.Dispose(); } } - return 0; - } - - /// - /// Writes the return value of the entry point to the console. - /// - /// The type of the return value. - /// The return value. - private static void DisplayReturnValue(T value) - { - if (!(value is QVoid)) - { - Console.WriteLine(value); - } } /// @@ -217,7 +194,7 @@ private static ParseArgument CreateArgumentParser(Func var (success, value) = parse(result.Tokens.Single().Value); if (!success) { - result.ErrorMessage = GetErrorMessage( + result.ErrorMessage = GetArgumentErrorMessage( ((OptionResult)result.Parent).Token.Value, result.Tokens.Single().Value, typeof(T)); } return value; @@ -236,7 +213,7 @@ private static @Result ParseRangeFromEnumerable(string option, string ar ? @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(GetErrorMessage(option, arg, typeof(QRange)))); + : @Result.Failure(GetArgumentErrorMessage(option, arg, typeof(QRange)))); /// /// Parses a long from a string. @@ -247,7 +224,7 @@ private static @Result ParseRangeFromEnumerable(string option, string ar private static @Result TryParseLong(string option, string str) => long.TryParse(str, out var result) ? @Result.Success(result) - : @Result.Failure(GetErrorMessage(option, str, typeof(long))); + : @Result.Failure(GetArgumentErrorMessage(option, str, typeof(long))); /// /// Returns an error message string for an argument parser. @@ -256,8 +233,27 @@ private static @Result TryParseLong(string option, string str) => /// The value of the argument being parsed. /// The expected type of the argument. /// - private static string GetErrorMessage(string option, string arg, Type type) => + 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(""); + } } /// From 88801bcc7667ac65b368a5352c7ef715a2a113e9 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Thu, 16 Apr 2020 14:55:00 -0700 Subject: [PATCH 39/70] Add TODO for option alias constants --- src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index 8c3bc57fbd9..8e23a017eae 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -82,6 +82,7 @@ internal static Argument ResultArgumentHandler private static async Task Main(string[] args) { var simulate = new Command("simulate", "(default) Run the program using a local simulator."); + // TODO: Replace the option aliases with constants defined in Microsoft.Quantum.QsCompiler.ReservedKeywords. TryCreateOption(new[] { "--simulator", "-s" }, () => @EntryPointAdapter.DefaultSimulator, "The name of the simulator to use.") From fb4cbcdb8dfef779a9855d2e805614fccdfd26d0 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Fri, 17 Apr 2020 15:50:51 -0700 Subject: [PATCH 40/70] Verify not more than one entry point --- .../CsharpGeneration/RewriteStep.fs | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/Simulation/CsharpGeneration/RewriteStep.fs b/src/Simulation/CsharpGeneration/RewriteStep.fs index 7b438877e9d..7edfbc913cc 100644 --- a/src/Simulation/CsharpGeneration/RewriteStep.fs +++ b/src/Simulation/CsharpGeneration/RewriteStep.fs @@ -6,6 +6,7 @@ namespace Microsoft.Quantum.QsCompiler.CsharpGeneration open System open System.Collections.Generic open System.IO +open Microsoft.CodeAnalysis open Microsoft.Quantum.QsCompiler open Microsoft.Quantum.QsCompiler.CsharpGeneration open Microsoft.Quantum.QsCompiler.DataTypes @@ -16,20 +17,30 @@ open Microsoft.Quantum.QsCompiler.Transformations.BasicTransformations type Emitter() = - let _AssemblyConstants = new Dictionary() + let _AssemblyConstants = new Dictionary<_, _>() + let mutable _Diagnostics = [] interface IRewriteStep with member this.Name = "CsharpGeneration" member this.Priority = -1 // doesn't matter because this rewrite step is the only one in the dll - member this.AssemblyConstants = _AssemblyConstants :> IDictionary - member this.GeneratedDiagnostics = null + member this.AssemblyConstants = upcast _AssemblyConstants + member this.GeneratedDiagnostics = upcast _Diagnostics - member this.ImplementsPreconditionVerification = false + member this.ImplementsPreconditionVerification = true member this.ImplementsPostconditionVerification = false member this.ImplementsTransformation = true - member this.PreconditionVerification _ = NotImplementedException() |> raise + member this.PreconditionVerification compilation = + if compilation.EntryPoints.Length > 1 then + _Diagnostics <- IRewriteStep.Diagnostic + (Message = "C# generation is not possible because there is more than one entry point.", + Severity = DiagnosticSeverity.Error, + Stage = IRewriteStep.Stage.PreconditionVerification) :: _Diagnostics + false + else + true + member this.PostconditionVerification _ = NotImplementedException() |> raise member this.Transformation (compilation, transformed) = @@ -54,12 +65,10 @@ type Emitter() = let content = SimulationCode.loadedViaTestNames source context if content <> null then CompilationLoader.GeneratedFile(source, dir, ".dll.g.cs", content) |> ignore - match Seq.tryExactlyOne compilation.EntryPoints with - | Some entryPoint -> - let callable = context.allCallables.[entryPoint] + if not compilation.EntryPoints.IsEmpty then + let callable = context.allCallables.[Seq.exactlyOne compilation.EntryPoints] let content = EntryPoint.generate context callable CompilationLoader.GeneratedFile(callable.SourceFile, dir, ".EntryPoint.g.cs", content) |> ignore - | None -> () transformed <- compilation true From 5d446735b9085c0ee19b1d67ffaf3d307fa4510d Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Fri, 17 Apr 2020 17:07:31 -0700 Subject: [PATCH 41/70] Use CommandLineArguments.SimulatorOption --- src/Simulation/CsharpGeneration/EntryPoint.fs | 43 +++++++++++++------ .../Microsoft.Quantum.CsharpGeneration.fsproj | 2 +- .../Resources/EntryPointDriver.cs | 3 +- ....Simulation.QCTraceSimulatorRuntime.csproj | 2 +- .../Microsoft.Quantum.QSharp.Core.csproj | 2 +- ...osoft.Quantum.Simulation.Simulators.csproj | 2 +- ...osoft.Quantum.Simulation.Simulators.csproj | 2 +- 7 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index a6a37699251..b4691e7a74b 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -2,6 +2,7 @@ open Microsoft.CodeAnalysis.CSharp open Microsoft.CodeAnalysis.CSharp.Syntax +open Microsoft.Quantum.QsCompiler.ReservedKeywords open Microsoft.Quantum.QsCompiler.SyntaxProcessing.SyntaxExtensions open Microsoft.Quantum.QsCompiler.SyntaxTokens open Microsoft.Quantum.QsCompiler.SyntaxTree @@ -18,6 +19,9 @@ type private Parameter = CsharpTypeName : string Description : string } +/// The name of the entry point constants class. +let private constantsClassName = "__QsEntryPointConstants__" + /// The name of the entry point adapter class. let private adapterClassName = "__QsEntryPointAdapter__" @@ -30,6 +34,22 @@ let private resultStructName = "__QsResult__" /// The name of the class containing extension methods for the result struct. let private resultExtensionsClassName = "__QsResultExtensions__" +/// Returns a public static property with a getter. +let private makeConstant name typeName value = + ``property-arrow_get`` typeName name [``public``; ``static``] + ``get`` (``=>`` value) + +/// A static class containing constants used by the entry point driver. +let private constantsClass = + ``class`` constantsClassName ``<<`` [] ``>>`` + ``:`` None ``,`` [] + [``internal``; ``static``] + ``{`` + [makeConstant "SimulatorOptionAliases" "System.Collections.Generic.IEnumerable" + (``new array`` (Some "") [``literal`` ("--" + fst CommandLineArguments.SimulatorOption) + ``literal`` ("-" + snd CommandLineArguments.SimulatorOption)])] + ``}`` + /// Returns a sequence of all of the named parameters in the argument tuple and their respective C# and Q# types. let rec private getParameters context doc = function | QsTupleItem variable -> @@ -135,17 +155,13 @@ let private getCustomSimulatorFactory name = /// Returns the class that adapts the entry point for use with the command-line parsing library and the driver. let private getAdapterClass context (entryPoint : QsCallable) = - let makeProperty name typeName value = - ``property-arrow_get`` typeName name [``public``; ``static``] - ``get`` (``=>`` value) - let summaryProperty = - makeProperty "Summary" "string" (``literal`` ((PrintSummary entryPoint.Documentation false).Trim ())) + makeConstant "Summary" "string" (``literal`` ((PrintSummary entryPoint.Documentation false).Trim ())) let defaultSimulator = context.assemblyConstants.TryGetValue "DefaultSimulator" |> snd |> (fun value -> if String.IsNullOrWhiteSpace value then "QuantumSimulator" else value) - let defaultSimulatorProperty = makeProperty "DefaultSimulator" "string" (``literal`` defaultSimulator) + let defaultSimulatorProperty = makeConstant "DefaultSimulator" "string" (``literal`` defaultSimulator) let parameters = getParameters context entryPoint.Documentation entryPoint.ArgumentTuple let members : seq = @@ -167,15 +183,17 @@ let private getAdapterClass context (entryPoint : QsCallable) = members ``}`` -/// Returns the source code for the entry point adapter. -let private getAdapter context (entryPoint : QsCallable) = +/// Returns the source code for the entry point constants and adapter classes. +let private getGeneratedClasses context (entryPoint : QsCallable) = let ns = ``namespace`` entryPoint.FullName.Namespace.Value ``{`` (Seq.map ``using`` SimulationCode.autoNamespaces) - [getAdapterClass context entryPoint] + [ + constantsClass + getAdapterClass context entryPoint + ] ``}`` - ``compilation unit`` [] [] [ns] |> ``with leading comments`` SimulationCode.autogenComment |> SimulationCode.formatSyntaxTree @@ -187,11 +205,12 @@ let private getDriver (entryPoint : QsCallable) = use reader = new StreamReader(stream) reader.ReadToEnd() .Replace("@Namespace", entryPoint.FullName.Namespace.Value) - .Replace("@EntryPointDriver", "__QsEntryPointDriver__") + .Replace("@EntryPointConstants", constantsClassName) .Replace("@EntryPointAdapter", adapterClassName) + .Replace("@EntryPointDriver", "__QsEntryPointDriver__") .Replace("@ResultExtensions", resultExtensionsClassName) .Replace("@Result", resultStructName) /// Generates C# source code for a standalone executable that runs the Q# entry point. let internal generate context entryPoint = - getAdapter context entryPoint + Environment.NewLine + getDriver entryPoint + getGeneratedClasses context entryPoint + Environment.NewLine + getDriver entryPoint diff --git a/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj b/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj index 10ac27966dc..31751fbe1fb 100644 --- a/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj +++ b/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj @@ -23,7 +23,7 @@ - + diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index 8e23a017eae..54d0536fe66 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -82,8 +82,7 @@ internal static Argument ResultArgumentHandler private static async Task Main(string[] args) { var simulate = new Command("simulate", "(default) Run the program using a local simulator."); - // TODO: Replace the option aliases with constants defined in Microsoft.Quantum.QsCompiler.ReservedKeywords. - TryCreateOption(new[] { "--simulator", "-s" }, + TryCreateOption(@EntryPointConstants.SimulatorOptionAliases, () => @EntryPointAdapter.DefaultSimulator, "The name of the simulator to use.") .Then(simulate.AddOption); diff --git a/src/Simulation/QCTraceSimulator.Tests/Tests.Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime.csproj b/src/Simulation/QCTraceSimulator.Tests/Tests.Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime.csproj index 81e1b1776cd..017930d97f3 100644 --- a/src/Simulation/QCTraceSimulator.Tests/Tests.Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime.csproj +++ b/src/Simulation/QCTraceSimulator.Tests/Tests.Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime.csproj @@ -1,4 +1,4 @@ - + diff --git a/src/Simulation/QsharpCore/Microsoft.Quantum.QSharp.Core.csproj b/src/Simulation/QsharpCore/Microsoft.Quantum.QSharp.Core.csproj index 23760447b52..6fa9abf9f3b 100644 --- a/src/Simulation/QsharpCore/Microsoft.Quantum.QSharp.Core.csproj +++ b/src/Simulation/QsharpCore/Microsoft.Quantum.QSharp.Core.csproj @@ -1,4 +1,4 @@ - + diff --git a/src/Simulation/Simulators.Tests/Tests.Microsoft.Quantum.Simulation.Simulators.csproj b/src/Simulation/Simulators.Tests/Tests.Microsoft.Quantum.Simulation.Simulators.csproj index 5235cdd9332..b489a310a22 100644 --- a/src/Simulation/Simulators.Tests/Tests.Microsoft.Quantum.Simulation.Simulators.csproj +++ b/src/Simulation/Simulators.Tests/Tests.Microsoft.Quantum.Simulation.Simulators.csproj @@ -1,4 +1,4 @@ - + diff --git a/src/Simulation/Simulators/Microsoft.Quantum.Simulation.Simulators.csproj b/src/Simulation/Simulators/Microsoft.Quantum.Simulation.Simulators.csproj index 1cc02f62456..650f7494f49 100644 --- a/src/Simulation/Simulators/Microsoft.Quantum.Simulation.Simulators.csproj +++ b/src/Simulation/Simulators/Microsoft.Quantum.Simulation.Simulators.csproj @@ -1,4 +1,4 @@ - + From ee15b5cc76b395a929d8faedfc5d5dd8a3a04a14 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Fri, 17 Apr 2020 17:14:15 -0700 Subject: [PATCH 42/70] Simplify function names --- src/Simulation/CsharpGeneration/EntryPoint.fs | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index b4691e7a74b..4919947243c 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -34,8 +34,8 @@ let private resultStructName = "__QsResult__" /// The name of the class containing extension methods for the result struct. let private resultExtensionsClassName = "__QsResultExtensions__" -/// Returns a public static property with a getter. -let private makeConstant name typeName value = +/// A public static property with a getter. +let private constant name typeName value = ``property-arrow_get`` typeName name [``public``; ``static``] ``get`` (``=>`` value) @@ -45,13 +45,13 @@ let private constantsClass = ``:`` None ``,`` [] [``internal``; ``static``] ``{`` - [makeConstant "SimulatorOptionAliases" "System.Collections.Generic.IEnumerable" + [constant "SimulatorOptionAliases" "System.Collections.Generic.IEnumerable" (``new array`` (Some "") [``literal`` ("--" + fst CommandLineArguments.SimulatorOption) ``literal`` ("-" + snd CommandLineArguments.SimulatorOption)])] ``}`` -/// Returns a sequence of all of the named parameters in the argument tuple and their respective C# and Q# types. -let rec private getParameters context doc = function +/// 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 -> @@ -68,10 +68,10 @@ let rec private getParameters context doc = function CsharpTypeName = SimulationCode.roslynTypeName context variable.Type Description = ParameterDescription doc name.Value } | InvalidName, _ -> Seq.empty - | QsTuple items -> items |> Seq.map (getParameters context doc) |> Seq.concat + | QsTuple items -> items |> Seq.map (parameters context doc) |> Seq.concat -/// Returns the custom argument handler for the given Q# type. -let private getArgumentHandler = +/// The custom argument handler for the given Q# type. +let private argumentHandler = function | UnitType -> Some "UnitArgumentHandler" | Result -> Some "ResultArgumentHandler" @@ -80,8 +80,8 @@ let private getArgumentHandler = | _ -> None >> Option.map (fun handler -> ``ident`` driverClassName <|.|> ``ident`` handler) -/// Returns a property containing a sequence of command-line options corresponding to each parameter given. -let private getParameterOptionsProperty parameters = +/// A property containing a sequence of command-line options corresponding to each parameter given. +let private parameterOptionsProperty parameters = let optionTypeName = "System.CommandLine.Option" let optionsEnumerableTypeName = sprintf "System.Collections.Generic.IEnumerable<%s>" optionTypeName let toKebabCaseIdent = ``ident`` "System.CommandLine.Parsing.StringExtensions.ToKebabCase" @@ -91,7 +91,7 @@ let private getParameterOptionsProperty parameters = then ``literal`` ("-" + name) else ``literal`` "--" <+> ``invoke`` toKebabCaseIdent ``(`` [``literal`` name] ``)`` let members = - getArgumentHandler qsType.Resolution + argumentHandler qsType.Resolution |> Option.map (fun handler -> ``ident`` "Argument" <-- handler :> ExpressionSyntax) |> Option.toList |> List.append [``ident`` "Required" <-- ``true``] @@ -105,24 +105,24 @@ let private getParameterOptionsProperty parameters = ``property-arrow_get`` optionsEnumerableTypeName "Options" [``public``; ``static``] ``get`` (``=>`` (``new array`` (Some optionTypeName) options)) -/// Returns the name of the parameter property for the given parameter name. -let private getParameterPropertyName (s : string) = +/// The name of the parameter property for the given parameter name. +let private parameterPropertyName (s : string) = s.Substring(0, 1).ToUpper() + s.Substring 1 -/// Returns a sequence of properties corresponding to each parameter given. -let private getParameterProperties = +/// A sequence of properties corresponding to each parameter given. +let private parameterProperties = Seq.map (fun { Name = name; CsharpTypeName = typeName } -> - ``prop`` typeName (getParameterPropertyName name) [``public``]) + ``prop`` typeName (parameterPropertyName name) [``public``]) -/// Returns the method for running the entry point using the parameter properties declared in the adapter. -let private getRunMethod context (entryPoint : QsCallable) = +/// The method for running the entry point using the parameter properties declared in the adapter. +let private runMethod context (entryPoint : QsCallable) = let entryPointName = sprintf "%s.%s" entryPoint.FullName.Namespace.Value entryPoint.FullName.Name.Value let returnTypeName = SimulationCode.roslynTypeName context entryPoint.Signature.ReturnType let taskTypeName = sprintf "System.Threading.Tasks.Task<%s>" returnTypeName let factoryParamName = "__factory__" let getArgExpr { Name = name; QsharpType = qsType } = - let property = ``ident`` "this" <|.|> ``ident`` (getParameterPropertyName name) + let property = ``ident`` "this" <|.|> ``ident`` (parameterPropertyName name) match qsType.Resolution with | ArrayType itemType -> // Convert the IEnumerable property into a QArray. @@ -133,7 +133,7 @@ let private getRunMethod context (entryPoint : QsCallable) = let callArgs : seq = Seq.concat [ Seq.singleton (upcast ``ident`` factoryParamName) - Seq.map getArgExpr (getParameters context entryPoint.Documentation entryPoint.ArgumentTuple) + Seq.map getArgExpr (parameters context entryPoint.Documentation entryPoint.ArgumentTuple) ] ``arrow_method`` taskTypeName "Run" ``<<`` [] ``>>`` @@ -141,8 +141,8 @@ let private getRunMethod context (entryPoint : QsCallable) = [``public``; ``async``] (Some (``=>`` (``await`` (``ident`` entryPointName <.> (``ident`` "Run", callArgs))))) -/// Returns a method that creates an instance of the default simulator if it is a custom simulator. -let private getCustomSimulatorFactory name = +/// A method that creates an instance of the default simulator if it is a custom simulator. +let private customSimulatorFactory name = let expr : ExpressionSyntax = match name with | "QuantumSimulator" | "ToffoliSimulator" | "ResourcesEstimator" -> @@ -153,27 +153,27 @@ let private getCustomSimulatorFactory name = [``public``; ``static``] (Some (``=>`` expr)) -/// Returns the class that adapts the entry point for use with the command-line parsing library and the driver. -let private getAdapterClass context (entryPoint : QsCallable) = +/// The class that adapts the entry point for use with the command-line parsing library and the driver. +let private adapterClass context (entryPoint : QsCallable) = let summaryProperty = - makeConstant "Summary" "string" (``literal`` ((PrintSummary entryPoint.Documentation false).Trim ())) + constant "Summary" "string" (``literal`` ((PrintSummary entryPoint.Documentation false).Trim ())) let defaultSimulator = context.assemblyConstants.TryGetValue "DefaultSimulator" |> snd |> (fun value -> if String.IsNullOrWhiteSpace value then "QuantumSimulator" else value) - let defaultSimulatorProperty = makeConstant "DefaultSimulator" "string" (``literal`` defaultSimulator) - let parameters = getParameters context entryPoint.Documentation entryPoint.ArgumentTuple + let defaultSimulatorProperty = constant "DefaultSimulator" "string" (``literal`` defaultSimulator) + let parameters = parameters context entryPoint.Documentation entryPoint.ArgumentTuple let members : seq = Seq.concat [ Seq.ofList [ summaryProperty defaultSimulatorProperty - getParameterOptionsProperty parameters - getCustomSimulatorFactory defaultSimulator - getRunMethod context entryPoint + parameterOptionsProperty parameters + customSimulatorFactory defaultSimulator + runMethod context entryPoint ] - getParameterProperties parameters |> Seq.map (fun property -> upcast property) + parameterProperties parameters |> Seq.map (fun property -> upcast property) ] ``class`` adapterClassName ``<<`` [] ``>>`` @@ -183,23 +183,23 @@ let private getAdapterClass context (entryPoint : QsCallable) = members ``}`` -/// Returns the source code for the entry point constants and adapter classes. -let private getGeneratedClasses context (entryPoint : QsCallable) = +/// The source code for the entry point constants and adapter classes. +let private generatedClasses context (entryPoint : QsCallable) = let ns = ``namespace`` entryPoint.FullName.Namespace.Value ``{`` (Seq.map ``using`` SimulationCode.autoNamespaces) [ constantsClass - getAdapterClass context entryPoint + adapterClass context entryPoint ] ``}`` ``compilation unit`` [] [] [ns] |> ``with leading comments`` SimulationCode.autogenComment |> SimulationCode.formatSyntaxTree -/// Returns the source code for the entry point driver. -let private getDriver (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) @@ -213,4 +213,4 @@ let private getDriver (entryPoint : QsCallable) = /// Generates C# source code for a standalone executable that runs the Q# entry point. let internal generate context entryPoint = - getGeneratedClasses context entryPoint + Environment.NewLine + getDriver entryPoint + generatedClasses context entryPoint + Environment.NewLine + driver entryPoint From e85a8060b6ad01cedf5ef2ee8275ad05cda7709a Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Fri, 17 Apr 2020 17:39:26 -0700 Subject: [PATCH 43/70] Add constants for simulator names --- src/Simulation/CsharpGeneration/EntryPoint.fs | 17 ++++++++++++----- .../Resources/EntryPointDriver.cs | 8 +++++--- src/Simulation/RoslynWrapper/WhiteNoise.fs | 3 +-- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 4919947243c..5b3430d36b7 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -34,8 +34,12 @@ let private resultStructName = "__QsResult__" /// The name of the class containing extension methods for the result struct. let private resultExtensionsClassName = "__QsResultExtensions__" -/// A public static property with a getter. +/// A public constant field. let private constant name typeName value = + ``field`` typeName name [``public``; ``const``] (``:=`` value |> Some) + +/// A public static property with a getter. +let private readonlyProperty name typeName value = ``property-arrow_get`` typeName name [``public``; ``static``] ``get`` (``=>`` value) @@ -45,9 +49,12 @@ let private constantsClass = ``:`` None ``,`` [] [``internal``; ``static``] ``{`` - [constant "SimulatorOptionAliases" "System.Collections.Generic.IEnumerable" + [readonlyProperty "SimulatorOptionAliases" "System.Collections.Generic.IEnumerable" (``new array`` (Some "") [``literal`` ("--" + fst CommandLineArguments.SimulatorOption) - ``literal`` ("-" + snd CommandLineArguments.SimulatorOption)])] + ``literal`` ("-" + snd CommandLineArguments.SimulatorOption)]) + constant "QuantumSimulator" "string" (``literal`` AssemblyConstants.QuantumSimulator) + constant "ToffoliSimulator" "string" (``literal`` AssemblyConstants.ToffoliSimulator) + constant "ResourcesEstimator" "string" (``literal`` AssemblyConstants.ResourcesEstimator)] ``}`` /// A sequence of all of the named parameters in the argument tuple and their respective C# and Q# types. @@ -156,12 +163,12 @@ let private customSimulatorFactory name = /// The class that adapts the entry point for use with the command-line parsing library and the driver. let private adapterClass context (entryPoint : QsCallable) = let summaryProperty = - constant "Summary" "string" (``literal`` ((PrintSummary entryPoint.Documentation false).Trim ())) + readonlyProperty "Summary" "string" (``literal`` ((PrintSummary entryPoint.Documentation false).Trim ())) let defaultSimulator = context.assemblyConstants.TryGetValue "DefaultSimulator" |> snd |> (fun value -> if String.IsNullOrWhiteSpace value then "QuantumSimulator" else value) - let defaultSimulatorProperty = constant "DefaultSimulator" "string" (``literal`` defaultSimulator) + let defaultSimulatorProperty = readonlyProperty "DefaultSimulator" "string" (``literal`` defaultSimulator) let parameters = parameters context entryPoint.Documentation entryPoint.ArgumentTuple let members : seq = diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index 54d0536fe66..b4da16ad23a 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -108,7 +108,7 @@ private static async Task Simulate(@EntryPointAdapter entryPoint, string si { switch (simulator) { - case "ResourcesEstimator": + case @EntryPointConstants.ResourcesEstimator: var resourcesEstimator = new ResourcesEstimator(); await entryPoint.Run(resourcesEstimator); Console.WriteLine(resourcesEstimator.ToTSV()); @@ -116,8 +116,10 @@ private static async Task Simulate(@EntryPointAdapter entryPoint, string si default: var (isCustom, createSimulator) = simulator switch { - "QuantumSimulator" => (false, new Func(() => new QuantumSimulator())), - "ToffoliSimulator" => (false, new Func(() => new ToffoliSimulator())), + @EntryPointConstants.QuantumSimulator => + (false, new Func(() => new QuantumSimulator())), + @EntryPointConstants.ToffoliSimulator => + (false, new Func(() => new ToffoliSimulator())), _ => (true, @EntryPointAdapter.CreateDefaultCustomSimulator) }; if (isCustom && simulator != @EntryPointAdapter.DefaultSimulator) diff --git a/src/Simulation/RoslynWrapper/WhiteNoise.fs b/src/Simulation/RoslynWrapper/WhiteNoise.fs index ba474c795a4..f693a1d614b 100644 --- a/src/Simulation/RoslynWrapper/WhiteNoise.fs +++ b/src/Simulation/RoslynWrapper/WhiteNoise.fs @@ -2,9 +2,7 @@ [] module WhiteNoise = - open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.CSharp - open Microsoft.CodeAnalysis.CSharp.Syntax let ``:`` = None let ``,`` = None @@ -31,3 +29,4 @@ module WhiteNoise = let ``override`` = SyntaxKind.OverrideKeyword let ``static`` = SyntaxKind.StaticKeyword let ``readonly`` = SyntaxKind.ReadOnlyKeyword + let ``const`` = SyntaxKind.ConstKeyword From c331f48e46e20abba81b02a9f0b2038568279b1b Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Fri, 17 Apr 2020 18:43:23 -0700 Subject: [PATCH 44/70] Protect namespace name instead of class names --- src/Simulation/CsharpGeneration/EntryPoint.fs | 33 ++------ .../Resources/EntryPointDriver.cs | 82 +++++++++---------- 2 files changed, 49 insertions(+), 66 deletions(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 5b3430d36b7..c48a252b942 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -19,20 +19,9 @@ type private Parameter = CsharpTypeName : string Description : string } -/// The name of the entry point constants class. -let private constantsClassName = "__QsEntryPointConstants__" - -/// The name of the entry point adapter class. -let private adapterClassName = "__QsEntryPointAdapter__" - -/// The name of the entry point driver class. -let private driverClassName = "__QsEntryPointDriver__" - -/// The name of the result struct. -let private resultStructName = "__QsResult__" - -/// The name of the class containing extension methods for the result struct. -let private resultExtensionsClassName = "__QsResultExtensions__" +/// The namespace in which to put generated code for the entry point. +let private generatedNamespace (entryPoint : QsCallable) = + entryPoint.FullName.Namespace.Value + ".__QsEntryPoint__" /// A public constant field. let private constant name typeName value = @@ -45,7 +34,7 @@ let private readonlyProperty name typeName value = /// A static class containing constants used by the entry point driver. let private constantsClass = - ``class`` constantsClassName ``<<`` [] ``>>`` + ``class`` "Constants" ``<<`` [] ``>>`` ``:`` None ``,`` [] [``internal``; ``static``] ``{`` @@ -85,7 +74,7 @@ let private argumentHandler = | BigInt -> Some "BigIntArgumentHandler" | Range -> Some "RangeArgumentHandler" | _ -> None - >> Option.map (fun handler -> ``ident`` driverClassName <|.|> ``ident`` handler) + >> Option.map (fun handler -> ``ident`` "Driver" <|.|> ``ident`` handler) /// A property containing a sequence of command-line options corresponding to each parameter given. let private parameterOptionsProperty parameters = @@ -183,7 +172,7 @@ let private adapterClass context (entryPoint : QsCallable) = parameterProperties parameters |> Seq.map (fun property -> upcast property) ] - ``class`` adapterClassName ``<<`` [] ``>>`` + ``class`` "EntryPoint" ``<<`` [] ``>>`` ``:`` None ``,`` [] [``internal``] ``{`` @@ -193,7 +182,7 @@ let private adapterClass context (entryPoint : QsCallable) = /// The source code for the entry point constants and adapter classes. let private generatedClasses context (entryPoint : QsCallable) = let ns = - ``namespace`` entryPoint.FullName.Namespace.Value + ``namespace`` (generatedNamespace entryPoint) ``{`` (Seq.map ``using`` SimulationCode.autoNamespaces) [ @@ -210,13 +199,7 @@ 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", entryPoint.FullName.Namespace.Value) - .Replace("@EntryPointConstants", constantsClassName) - .Replace("@EntryPointAdapter", adapterClassName) - .Replace("@EntryPointDriver", "__QsEntryPointDriver__") - .Replace("@ResultExtensions", resultExtensionsClassName) - .Replace("@Result", resultStructName) + reader.ReadToEnd().Replace("@Namespace", generatedNamespace entryPoint) /// 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/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index b4da16ad23a..d14a2c893d2 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -14,7 +14,7 @@ /// /// The entry point driver is the entry point for the C# application that executes the Q# entry point. /// - internal static class @EntryPointDriver + internal static class Driver { /// /// The argument handler for the Q# Unit type. @@ -82,14 +82,14 @@ internal static Argument ResultArgumentHandler private static async Task Main(string[] args) { var simulate = new Command("simulate", "(default) Run the program using a local simulator."); - TryCreateOption(@EntryPointConstants.SimulatorOptionAliases, - () => @EntryPointAdapter.DefaultSimulator, + TryCreateOption(Constants.SimulatorOptionAliases, + () => EntryPoint.DefaultSimulator, "The name of the simulator to use.") .Then(simulate.AddOption); - simulate.Handler = CommandHandler.Create<@EntryPointAdapter, string>(Simulate); + simulate.Handler = CommandHandler.Create(Simulate); - var root = new RootCommand(@EntryPointAdapter.Summary) { simulate }; - foreach (var option in @EntryPointAdapter.Options) { root.AddGlobalOption(option); } + 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); } @@ -101,14 +101,14 @@ private static async Task Main(string[] args) /// /// Simulates the entry point. /// - /// The entry point adapter. + /// The entry point. /// The simulator to use. /// The exit code. - private static async Task Simulate(@EntryPointAdapter entryPoint, string simulator) + private static async Task Simulate(EntryPoint entryPoint, string simulator) { switch (simulator) { - case @EntryPointConstants.ResourcesEstimator: + case Constants.ResourcesEstimator: var resourcesEstimator = new ResourcesEstimator(); await entryPoint.Run(resourcesEstimator); Console.WriteLine(resourcesEstimator.ToTSV()); @@ -116,13 +116,13 @@ private static async Task Simulate(@EntryPointAdapter entryPoint, string si default: var (isCustom, createSimulator) = simulator switch { - @EntryPointConstants.QuantumSimulator => + Constants.QuantumSimulator => (false, new Func(() => new QuantumSimulator())), - @EntryPointConstants.ToffoliSimulator => + Constants.ToffoliSimulator => (false, new Func(() => new ToffoliSimulator())), - _ => (true, @EntryPointAdapter.CreateDefaultCustomSimulator) + _ => (true, EntryPoint.CreateDefaultCustomSimulator) }; - if (isCustom && simulator != @EntryPointAdapter.DefaultSimulator) + if (isCustom && simulator != EntryPoint.DefaultSimulator) { DisplayCustomSimulatorError(simulator); return 1; @@ -136,10 +136,10 @@ private static async Task Simulate(@EntryPointAdapter entryPoint, string si /// /// Runs the entry point on a simulator and displays its return value. /// - /// The entry point adapter. + /// The entry point. /// A function that creates an instance of the simulator to use. private static async Task DisplayEntryPointResult( - @EntryPointAdapter entryPoint, Func createSimulator) + EntryPoint entryPoint, Func createSimulator) { var simulator = createSimulator(); try @@ -169,17 +169,17 @@ private static async Task DisplayEntryPointResult( /// The option's aliases. /// A function that returns the option's default value. /// The option's description. - /// - private static @Result> TryCreateOption( + /// The result of trying to create the option. + private static Result> TryCreateOption( IEnumerable aliases, Func getDefaultValue, string description = null) { static bool isAliasAvailable(string alias) => - !@EntryPointAdapter.Options.SelectMany(option => option.RawAliases).Contains(alias); + !EntryPoint.Options.SelectMany(option => option.RawAliases).Contains(alias); var validAliases = aliases.Where(isAliasAvailable); return validAliases.Any() - ? @Result>.Success(new Option(validAliases.ToArray(), getDefaultValue, description)) - : @Result>.Failure(); + ? Result>.Success(new Option(validAliases.ToArray(), getDefaultValue, description)) + : Result>.Failure(); } /// @@ -209,13 +209,13 @@ private static ParseArgument CreateArgumentParser(Func /// 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) => + 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))) + ? 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)))); + ? 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. @@ -223,10 +223,10 @@ private static @Result ParseRangeFromEnumerable(string option, string ar /// 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) => + private static Result TryParseLong(string option, string str) => long.TryParse(str, out var result) - ? @Result.Success(result) - : @Result.Failure(GetArgumentErrorMessage(option, str, typeof(long))); + ? Result.Success(result) + : Result.Failure(GetArgumentErrorMessage(option, str, typeof(long))); /// /// Returns an error message string for an argument parser. @@ -234,7 +234,7 @@ private static @Result TryParseLong(string option, string str) => /// 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}."; @@ -262,29 +262,29 @@ private static void DisplayCustomSimulatorError(string name) /// The result of a process that can either succeed or fail. /// /// The type of the result value. - internal struct @Result + internal struct Result { public bool IsSuccess { get; } public T Value { get; } public bool IsFailure { get => !IsSuccess; } public string ErrorMessage { get; } - private @Result(bool isSuccess, T value, string errorMessage) + private Result(bool isSuccess, T value, string errorMessage) { IsSuccess = isSuccess; Value = value; ErrorMessage = errorMessage; } - public static @Result Success(T value) => new @Result(true, value, default); + public static Result Success(T value) => new Result(true, value, default); - public static @Result Failure(string errorMessage = null) => new @Result(false, default, errorMessage); + public static Result Failure(string errorMessage = null) => new Result(false, default, errorMessage); } /// - /// Extensions method for . + /// Extensions method for . /// - internal static class @ResultExtensions + internal static class ResultExtensions { /// /// Sequentially composes two results, passing the value of the first result to another result-producing @@ -298,8 +298,8 @@ internal static class @ResultExtensions /// 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); + 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. @@ -310,10 +310,10 @@ internal static @Result Bind(this @Result result, Func /// 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<@Result> results) => + 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); + ? 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. @@ -321,7 +321,7 @@ internal static @Result> Sequence(this IEnumerable<@Result> /// 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) + internal static void Then(this Result result, Action onSuccess) { if (result.IsSuccess) { @@ -340,7 +340,7 @@ internal static void Then(this @Result result, Action onSuccess) /// /// The value of the first successful result, or default if none of the results were successful. /// - internal static T Choose(this IEnumerable<@Result> results, Action> onError) + internal static T Choose(this IEnumerable> results, Action> onError) { if (results.Any(result => result.IsSuccess)) { From e5a790ae6d26bdaecd114698b682827bc10cd1ef Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Fri, 17 Apr 2020 19:42:41 -0700 Subject: [PATCH 45/70] Support tab completion for simulators --- .../Resources/EntryPointDriver.cs | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index d14a2c893d2..b70e52b9a80 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -4,7 +4,10 @@ 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; @@ -82,10 +85,18 @@ internal static Argument ResultArgumentHandler private static async Task Main(string[] args) { var simulate = new Command("simulate", "(default) Run the program using a local simulator."); - TryCreateOption(Constants.SimulatorOptionAliases, - () => EntryPoint.DefaultSimulator, - "The name of the simulator to use.") - .Then(simulate.AddOption); + TryCreateOption( + Constants.SimulatorOptionAliases, + () => 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 }; @@ -95,7 +106,11 @@ private static async Task Main(string[] args) foreach (var option in simulate.Options) { root.AddOption(option); } root.Handler = simulate.Handler; - return await root.InvokeAsync(args); + return await new CommandLineBuilder(root) + .UseDefaults() + .UseHelpBuilder(context => new QsHelpBuilder(context.Console)) + .Build() + .InvokeAsync(args); } /// @@ -258,6 +273,21 @@ private static void DisplayCustomSimulatorError(string name) } } + /// + /// 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. /// @@ -282,7 +312,7 @@ private Result(bool isSuccess, T value, string errorMessage) } /// - /// Extensions method for . + /// Extension methods for . /// internal static class ResultExtensions { From bc7a216269021fc21ae4e8b2bc4f60f65c3940f5 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 20 Apr 2020 10:26:10 -0700 Subject: [PATCH 46/70] Consistent trailing newlines for generatedClasses and driver --- src/Simulation/CsharpGeneration/EntryPoint.fs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index c48a252b942..2817ffc0e27 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -193,6 +193,7 @@ let private generatedClasses context (entryPoint : QsCallable) = ``compilation unit`` [] [] [ns] |> ``with leading comments`` SimulationCode.autogenComment |> SimulationCode.formatSyntaxTree + |> fun code -> code + Environment.NewLine /// The source code for the entry point driver. let private driver (entryPoint : QsCallable) = @@ -203,4 +204,4 @@ let private driver (entryPoint : QsCallable) = /// Generates C# source code for a standalone executable that runs the Q# entry point. let internal generate context entryPoint = - generatedClasses context entryPoint + Environment.NewLine + driver entryPoint + generatedClasses context entryPoint + driver entryPoint From 0cb3ef0bd02ebb7db9387e8a65b8b2769b470cc5 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 20 Apr 2020 16:34:06 -0700 Subject: [PATCH 47/70] Add basic unit tests --- .../CsharpGeneration.Tests/Circuits/Core.qs | 10 ++ .../Circuits/EntryPointTests.qs | 25 ++++ .../CsharpGeneration.Tests/EntryPointTests.fs | 126 ++++++++++++++++++ .../Tests.CsharpGeneration.fsproj | 11 ++ src/Simulation/CsharpGeneration/EntryPoint.fs | 10 +- 5 files changed, 176 insertions(+), 6 deletions(-) create mode 100644 src/Simulation/CsharpGeneration.Tests/Circuits/Core.qs create mode 100644 src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs create mode 100644 src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs diff --git a/src/Simulation/CsharpGeneration.Tests/Circuits/Core.qs b/src/Simulation/CsharpGeneration.Tests/Circuits/Core.qs new file mode 100644 index 00000000000..8dbddd57b9e --- /dev/null +++ b/src/Simulation/CsharpGeneration.Tests/Circuits/Core.qs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Core { + @Attribute() + newtype Attribute = Unit; + + @Attribute() + newtype EntryPoint = Unit; +} diff --git a/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs b/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs new file mode 100644 index 00000000000..d57f045b39a --- /dev/null +++ b/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation ReturnUnit() : Unit { } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation ReturnInt() : Int { + return 42; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation ReturnString() : String { + return "Hello, World!"; + } +} diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs new file mode 100644 index 00000000000..a963d5012c7 --- /dev/null +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +module Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint + +open System +open System.Collections.Immutable +open System.IO +open System.Reflection +open System.Threading.Tasks +open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.CSharp +open Microsoft.Quantum.QsCompiler.CompilationBuilder +open Microsoft.Quantum.QsCompiler.CsharpGeneration +open Microsoft.Quantum.QsCompiler.DataTypes +open Xunit + + +/// The path to the Q# file that provides the Microsoft.Quantum.Core namespace. +let private coreFile = Path.Combine ("Circuits", "Core.qs") |> Path.GetFullPath + +/// The path to the Q# file that contains the test cases. +let private testFile = Path.Combine ("Circuits", "EntryPointTests.qs") |> Path.GetFullPath + +/// The namespace used for the test cases. +let private testNamespace = "Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint" + +/// The test cases. +let private tests = File.ReadAllText testFile |> fun text -> text.Split "// ---" + +/// Compiles Q# source code into a syntax tree with the list of entry points names. +let private compileQsharp source = + let uri name = Uri ("file://" + name) + let fileManager name content = + CompilationUnitManager.InitializeFileManager (uri name, content) + + use compilationManager = new CompilationUnitManager (isExecutable = true) + let fileManagers = ImmutableHashSet.Create (fileManager coreFile (File.ReadAllText coreFile), + fileManager testFile source) + compilationManager.AddOrUpdateSourceFilesAsync fileManagers |> ignore + let compilation = compilationManager.Build () + Assert.Empty (compilation.Diagnostics ()) + compilation.BuiltCompilation.Namespaces, compilation.BuiltCompilation.EntryPoints + +/// Generates C# source code for the given test case number. +let private generateCsharp testNum = + let syntaxTree, entryPoints = compileQsharp tests.[testNum - 1] + let context = CodegenContext.Create syntaxTree + let entryPoint = context.allCallables.[Seq.exactlyOne entryPoints] + [ + SimulationCode.generate (NonNullable<_>.New testFile) context + EntryPoint.generate context entryPoint + ] + +/// The full path to a referenced assembly given its short name. +let private referencedAssembly name = + (AppContext.GetData "TRUSTED_PLATFORM_ASSEMBLIES" :?> string).Split ';' + |> Seq.find (fun path -> String.Equals (Path.GetFileNameWithoutExtension path, name, + StringComparison.InvariantCultureIgnoreCase)) + +/// Compiles the C# sources into an assembly. +let private compileCsharp (sources : string seq) = + let references : MetadataReference list = + [ + "netstandard" + "System.Collections.Immutable" + "System.CommandLine" + "System.Console" + "System.Linq" + "System.Private.CoreLib" + "System.Runtime" + "System.Runtime.Extensions" + "System.Runtime.Numerics" + "Microsoft.Quantum.QSharp.Core" + "Microsoft.Quantum.Runtime.Core" + "Microsoft.Quantum.Simulation.Common" + "Microsoft.Quantum.Simulation.Simulators" + ] + |> List.map (fun name -> upcast MetadataReference.CreateFromFile (referencedAssembly name)) + + let syntaxTrees = sources |> Seq.map CSharpSyntaxTree.ParseText + let compilation = CSharpCompilation.Create ("GeneratedEntryPoint", syntaxTrees, references) + use stream = new MemoryStream () + let result = compilation.Emit stream + Assert.True (result.Success, String.Join ("\n", result.Diagnostics)) + Assert.Equal (0L, stream.Seek (0L, SeekOrigin.Begin)) + Assembly.Load (stream.ToArray ()) + +/// The assembly for the given test case. +let private testAssembly = generateCsharp >> compileCsharp + +/// Runs the entry point driver in the assembly with the given command-line arguments, and returns the output. +let private run (assembly : Assembly) (args : string[]) = + let driver = assembly.GetType (EntryPoint.generatedNamespace testNamespace + ".Driver") + let main = driver.GetMethod("Main", BindingFlags.NonPublic ||| BindingFlags.Static) + + let previousOut = Console.Out + use stream = new StringWriter () + Console.SetOut stream + let exitCode = main.Invoke (null, [| args |]) :?> Task |> Async.AwaitTask |> Async.RunSynchronously + Console.SetOut previousOut + stream.ToString (), exitCode + +/// Asserts that running the entry point in the assembly with the given arguments succeeds and yields the expected +/// output. +let private yields expected (assembly, args) = + let output, exitCode = run assembly args + Assert.Equal (0, exitCode) + Assert.Equal (expected, output.TrimEnd ()) + +/// Asserts that running the entry point in the assembly with the given argument fails. +let private fails (assembly, args) = + let _, exitCode = run assembly args + Assert.NotEqual (0, exitCode) + +[] +let ``Entry point returns Unit`` () = + (testAssembly 1, Array.empty) |> yields "" + +[] +let ``Entry point returns Int`` () = + (testAssembly 2, Array.empty) |> yields "42" + +[] +let ``Entry point returns String`` () = + (testAssembly 3, Array.empty) |> yields "Hello, World!" diff --git a/src/Simulation/CsharpGeneration.Tests/Tests.CsharpGeneration.fsproj b/src/Simulation/CsharpGeneration.Tests/Tests.CsharpGeneration.fsproj index ec9defecf97..617f1637dbb 100644 --- a/src/Simulation/CsharpGeneration.Tests/Tests.CsharpGeneration.fsproj +++ b/src/Simulation/CsharpGeneration.Tests/Tests.CsharpGeneration.fsproj @@ -28,8 +28,15 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + @@ -39,6 +46,7 @@ + all @@ -48,7 +56,10 @@ + + + diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 2817ffc0e27..b4d42d88a2b 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -20,8 +20,7 @@ type private Parameter = Description : string } /// The namespace in which to put generated code for the entry point. -let private generatedNamespace (entryPoint : QsCallable) = - entryPoint.FullName.Namespace.Value + ".__QsEntryPoint__" +let internal generatedNamespace entryPointNamespace = entryPointNamespace + ".__QsEntryPoint__" /// A public constant field. let private constant name typeName value = @@ -102,8 +101,7 @@ let private parameterOptionsProperty parameters = ``get`` (``=>`` (``new array`` (Some optionTypeName) options)) /// The name of the parameter property for the given parameter name. -let private parameterPropertyName (s : string) = - s.Substring(0, 1).ToUpper() + s.Substring 1 +let private parameterPropertyName (s : string) = s.Substring(0, 1).ToUpper() + s.Substring 1 /// A sequence of properties corresponding to each parameter given. let private parameterProperties = @@ -182,7 +180,7 @@ let private adapterClass context (entryPoint : QsCallable) = /// The source code for the entry point constants and adapter classes. let private generatedClasses context (entryPoint : QsCallable) = let ns = - ``namespace`` (generatedNamespace entryPoint) + ``namespace`` (generatedNamespace entryPoint.FullName.Namespace.Value) ``{`` (Seq.map ``using`` SimulationCode.autoNamespaces) [ @@ -200,7 +198,7 @@ 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) + reader.ReadToEnd().Replace("@Namespace", generatedNamespace entryPoint.FullName.Namespace.Value) /// Generates C# source code for a standalone executable that runs the Q# entry point. let internal generate context entryPoint = From e6b266cacbd160a19117354aefb9d1c5ff688440 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 20 Apr 2020 17:33:10 -0700 Subject: [PATCH 48/70] Add tests for option types --- .../Circuits/EntryPointTests.qs | 98 +++++++++++++++++++ .../CsharpGeneration.Tests/EntryPointTests.fs | 89 ++++++++++++++++- 2 files changed, 182 insertions(+), 5 deletions(-) diff --git a/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs b/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs index d57f045b39a..81494cbe0b6 100644 --- a/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs +++ b/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs @@ -1,6 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +// +// No Options +// + namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { @EntryPoint() operation ReturnUnit() : Unit { } @@ -23,3 +27,97 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { return "Hello, World!"; } } + +// --- + +// +// Single Option +// + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation AcceptInt(n : Int) : Int { + return n; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation AcceptBigInt(n : BigInt) : BigInt { + return n; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation AcceptDouble(n : Double) : Double { + return n; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation AcceptBool(b : Bool) : Bool { + return b; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation AcceptPauli(p : Pauli) : Pauli { + return p; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation AcceptResult(r : Result) : Result { + return r; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation AcceptRange(r : Range) : Range { + return r; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation AcceptString(s : String) : String { + return s; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation AcceptStringArray(xs : String[]) : String[] { + return xs; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation AcceptUnit(u : Unit) : Unit { + return u; + } +} diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index a963d5012c7..84bfc907c36 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -5,6 +5,7 @@ module Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint open System open System.Collections.Immutable +open System.Globalization open System.IO open System.Reflection open System.Threading.Tasks @@ -13,6 +14,7 @@ open Microsoft.CodeAnalysis.CSharp open Microsoft.Quantum.QsCompiler.CompilationBuilder open Microsoft.Quantum.QsCompiler.CsharpGeneration open Microsoft.Quantum.QsCompiler.DataTypes +open Microsoft.VisualStudio.LanguageServer.Protocol open Xunit @@ -39,7 +41,10 @@ let private compileQsharp source = fileManager testFile source) compilationManager.AddOrUpdateSourceFilesAsync fileManagers |> ignore let compilation = compilationManager.Build () - Assert.Empty (compilation.Diagnostics ()) + let errors = + compilation.Diagnostics () + |> Seq.filter (fun diagnostic -> diagnostic.Severity = DiagnosticSeverity.Error) + Assert.Empty errors compilation.BuiltCompilation.Namespaces, compilation.BuiltCompilation.EntryPoints /// Generates C# source code for the given test case number. @@ -93,12 +98,16 @@ let private testAssembly = generateCsharp >> compileCsharp let private run (assembly : Assembly) (args : string[]) = let driver = assembly.GetType (EntryPoint.generatedNamespace testNamespace + ".Driver") let main = driver.GetMethod("Main", BindingFlags.NonPublic ||| BindingFlags.Static) - + let previousCulture = CultureInfo.DefaultThreadCurrentCulture let previousOut = Console.Out + + CultureInfo.DefaultThreadCurrentCulture <- CultureInfo ("en-US", false) use stream = new StringWriter () Console.SetOut stream let exitCode = main.Invoke (null, [| args |]) :?> Task |> Async.AwaitTask |> Async.RunSynchronously Console.SetOut previousOut + CultureInfo.DefaultThreadCurrentCulture <- previousCulture + stream.ToString (), exitCode /// Asserts that running the entry point in the assembly with the given arguments succeeds and yields the expected @@ -113,14 +122,84 @@ let private fails (assembly, args) = let _, exitCode = run assembly args Assert.NotEqual (0, exitCode) + +// No Option + [] -let ``Entry point returns Unit`` () = +let ``Returns Unit`` () = (testAssembly 1, Array.empty) |> yields "" [] -let ``Entry point returns Int`` () = +let ``Returns Int`` () = (testAssembly 2, Array.empty) |> yields "42" [] -let ``Entry point returns String`` () = +let ``Returns String`` () = (testAssembly 3, Array.empty) |> yields "Hello, World!" + + +// Single Option + +[] +let ``Accepts Int`` () = + (testAssembly 4, [| "-n"; "42" |]) |> yields "42" + +[] +let ``Accepts BigInt`` () = + (testAssembly 5, [| "-n"; "9223372036854775808" |]) |> yields "9223372036854775808" + +[] +let ``Accepts Double`` () = + (testAssembly 6, [| "-n"; "4.2" |]) |> yields "4.2" + +[] +let ``Accepts Bool`` () = + let assembly = testAssembly 7 + (assembly, [| "-b" |]) |> yields "True" + (assembly, [| "-b"; "false" |]) |> yields "False" + (assembly, [| "-b"; "true" |]) |> yields "True" + +[] +let ``Accepts Pauli`` () = + let assembly = testAssembly 8 + (assembly, [| "-p"; "PauliI" |]) |> yields "PauliI" + (assembly, [| "-p"; "PauliX" |]) |> yields "PauliX" + (assembly, [| "-p"; "PauliY" |]) |> yields "PauliY" + (assembly, [| "-p"; "PauliZ" |]) |> yields "PauliZ" + +[] +let ``Accepts Result`` () = + let assembly = testAssembly 9 + (assembly, [| "-r"; "Zero" |]) |> yields "Zero" + (assembly, [| "-r"; "One" |]) |> yields "One" + +[] +let ``Accepts Range`` () = + let assembly = testAssembly 10 + (assembly, [| "-r"; "0..0" |]) |> yields "0..1..0" + (assembly, [| "-r"; "0..1" |]) |> yields "0..1..1" + (assembly, [| "-r"; "0..2..10" |]) |> yields "0..2..10" + (assembly, [| "-r"; "0"; "..1" |]) |> yields "0..1..1" + (assembly, [| "-r"; "0.."; "1" |]) |> yields "0..1..1" + (assembly, [| "-r"; "0"; ".."; "1" |]) |> yields "0..1..1" + (assembly, [| "-r"; "0"; "..2"; "..10" |]) |> yields "0..2..10" + (assembly, [| "-r"; "0.."; "2"; "..10" |]) |> yields "0..2..10" + (assembly, [| "-r"; "0"; ".."; "2"; ".."; "10" |]) |> yields "0..2..10" + (assembly, [| "-r"; "0"; "1" |]) |> yields "0..1..1" + (assembly, [| "-r"; "0"; "2"; "10" |]) |> yields "0..2..10" + +[] +let ``Accepts String`` () = + (testAssembly 11, [| "-s"; "Hello, World!" |]) |> yields "Hello, World!" + +[] +let ``Accepts String array`` () = + let assembly = testAssembly 12 + (assembly, [| "--xs"; "foo" |]) |> yields "[foo]" + (assembly, [| "--xs"; "foo"; "bar" |]) |> yields "[foo,bar]" + (assembly, [| "--xs"; "foo bar"; "baz" |]) |> yields "[foo bar,baz]" + (assembly, [| "--xs"; "foo"; "bar"; "baz" |]) |> yields "[foo,bar,baz]" + +[] +let ``Accepts Unit`` () = + (testAssembly 13, [| "-u"; "()" |]) |> yields "" From 84b71b5adf7851408544e97e6af60c8f12666bbe Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 20 Apr 2020 17:46:01 -0700 Subject: [PATCH 49/70] Add failure cases --- .../CsharpGeneration.Tests/EntryPointTests.fs | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index 84bfc907c36..cdbd0c95f8a 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -119,8 +119,8 @@ let private yields expected (assembly, args) = /// Asserts that running the entry point in the assembly with the given argument fails. let private fails (assembly, args) = - let _, exitCode = run assembly args - Assert.NotEqual (0, exitCode) + let output, exitCode = run assembly args + Assert.True (0 <> exitCode, "Succeeded unexpectedly:\n" + output) // No Option @@ -142,15 +142,21 @@ let ``Returns String`` () = [] let ``Accepts Int`` () = - (testAssembly 4, [| "-n"; "42" |]) |> yields "42" + let assembly = testAssembly 4 + (assembly, [| "-n"; "42" |]) |> yields "42" + (assembly, [| "-n"; "4.2" |]) |> fails + (assembly, [| "-n"; "9223372036854775808" |]) |> fails [] let ``Accepts BigInt`` () = - (testAssembly 5, [| "-n"; "9223372036854775808" |]) |> yields "9223372036854775808" + let assembly = testAssembly 5 + (assembly, [| "-n"; "9223372036854775808" |]) |> yields "9223372036854775808" + (assembly, [| "-n"; "foo" |]) |> fails [] let ``Accepts Double`` () = (testAssembly 6, [| "-n"; "4.2" |]) |> yields "4.2" + (testAssembly 6, [| "-n"; "foo" |]) |> fails [] let ``Accepts Bool`` () = @@ -158,6 +164,7 @@ let ``Accepts Bool`` () = (assembly, [| "-b" |]) |> yields "True" (assembly, [| "-b"; "false" |]) |> yields "False" (assembly, [| "-b"; "true" |]) |> yields "True" + (assembly, [| "-b"; "one" |]) |> fails [] let ``Accepts Pauli`` () = @@ -166,12 +173,18 @@ let ``Accepts Pauli`` () = (assembly, [| "-p"; "PauliX" |]) |> yields "PauliX" (assembly, [| "-p"; "PauliY" |]) |> yields "PauliY" (assembly, [| "-p"; "PauliZ" |]) |> yields "PauliZ" + (assembly, [| "-p"; "PauliW" |]) |> fails [] let ``Accepts Result`` () = let assembly = testAssembly 9 (assembly, [| "-r"; "Zero" |]) |> yields "Zero" + (assembly, [| "-r"; "zero" |]) |> yields "Zero" (assembly, [| "-r"; "One" |]) |> yields "One" + (assembly, [| "-r"; "one" |]) |> yields "One" + (assembly, [| "-r"; "0" |]) |> yields "Zero" + (assembly, [| "-r"; "1" |]) |> yields "One" + (assembly, [| "-r"; "Two" |]) |> fails [] let ``Accepts Range`` () = @@ -187,6 +200,12 @@ let ``Accepts Range`` () = (assembly, [| "-r"; "0"; ".."; "2"; ".."; "10" |]) |> yields "0..2..10" (assembly, [| "-r"; "0"; "1" |]) |> yields "0..1..1" (assembly, [| "-r"; "0"; "2"; "10" |]) |> yields "0..2..10" + (assembly, [| "-r"; "0" |]) |> fails + (assembly, [| "-r"; "0.." |]) |> fails + (assembly, [| "-r"; "0..2.." |]) |> fails + (assembly, [| "-r"; "0..2..3.." |]) |> fails + (assembly, [| "-r"; "0..2..3..4" |]) |> fails + (assembly, [| "-r"; "0"; "1"; "2"; "3" |]) |> fails [] let ``Accepts String`` () = @@ -202,4 +221,6 @@ let ``Accepts String array`` () = [] let ``Accepts Unit`` () = - (testAssembly 13, [| "-u"; "()" |]) |> yields "" + let assembly = testAssembly 13 + (assembly, [| "-u"; "()" |]) |> yields "" + (assembly, [| "-u"; "42" |]) |> fails From d2afefbb5890465baa2c39232981123c4bbba9e5 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 20 Apr 2020 18:25:23 -0700 Subject: [PATCH 50/70] Better test syntax --- .../CsharpGeneration.Tests/EntryPointTests.fs | 134 ++++++++++-------- 1 file changed, 74 insertions(+), 60 deletions(-) diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index cdbd0c95f8a..4bdbe7bad65 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -117,110 +117,124 @@ let private yields expected (assembly, args) = Assert.Equal (0, exitCode) Assert.Equal (expected, output.TrimEnd ()) -/// Asserts that running the entry point in the assembly with the given argument fails. +/// Asserts that running the entry point in the assembly with the given arguments fails. let private fails (assembly, args) = let output, exitCode = run assembly args - Assert.True (0 <> exitCode, "Succeeded unexpectedly:\n" + output) + Assert.True (0 <> exitCode, "Succeeded unexpectedly:" + Environment.NewLine + output) +/// A tuple of the test assembly for the given test number, and the given argument string converted into an array. +let test testNum = + let assembly = testAssembly testNum + fun args -> assembly, Array.ofList args // No Option [] let ``Returns Unit`` () = - (testAssembly 1, Array.empty) |> yields "" + let given = test 1 + given [] |> yields "" [] let ``Returns Int`` () = - (testAssembly 2, Array.empty) |> yields "42" + let given = test 2 + given [] |> yields "42" [] let ``Returns String`` () = - (testAssembly 3, Array.empty) |> yields "Hello, World!" + let given = test 3 + given [] |> yields "Hello, World!" // Single Option [] let ``Accepts Int`` () = - let assembly = testAssembly 4 - (assembly, [| "-n"; "42" |]) |> yields "42" - (assembly, [| "-n"; "4.2" |]) |> fails - (assembly, [| "-n"; "9223372036854775808" |]) |> fails + let given = test 4 + given ["-n"; "42"] |> yields "42" + given ["-n"; "4.2"] |> fails + given ["-n"; "9223372036854775807"] |> yields "9223372036854775807" + given ["-n"; "9223372036854775808"] |> fails + given ["-n"; "foo"] |> fails [] let ``Accepts BigInt`` () = - let assembly = testAssembly 5 - (assembly, [| "-n"; "9223372036854775808" |]) |> yields "9223372036854775808" - (assembly, [| "-n"; "foo" |]) |> fails + let given = test 5 + given ["-n"; "42"] |> yields "42" + given ["-n"; "4.2"] |> fails + given ["-n"; "9223372036854775807"] |> yields "9223372036854775807" + given ["-n"; "9223372036854775808"] |> yields "9223372036854775808" + given ["-n"; "foo"] |> fails [] let ``Accepts Double`` () = - (testAssembly 6, [| "-n"; "4.2" |]) |> yields "4.2" - (testAssembly 6, [| "-n"; "foo" |]) |> fails + let given = test 6 + given ["-n"; "4.2"] |> yields "4.2" + given ["-n"; "foo"] |> fails [] let ``Accepts Bool`` () = - let assembly = testAssembly 7 - (assembly, [| "-b" |]) |> yields "True" - (assembly, [| "-b"; "false" |]) |> yields "False" - (assembly, [| "-b"; "true" |]) |> yields "True" - (assembly, [| "-b"; "one" |]) |> fails + let given = test 7 + given ["-b"] |> yields "True" + given ["-b"; "false"] |> yields "False" + given ["-b"; "true"] |> yields "True" + given ["-b"; "one"] |> fails [] let ``Accepts Pauli`` () = - let assembly = testAssembly 8 - (assembly, [| "-p"; "PauliI" |]) |> yields "PauliI" - (assembly, [| "-p"; "PauliX" |]) |> yields "PauliX" - (assembly, [| "-p"; "PauliY" |]) |> yields "PauliY" - (assembly, [| "-p"; "PauliZ" |]) |> yields "PauliZ" - (assembly, [| "-p"; "PauliW" |]) |> fails + let given = test 8 + given ["-p"; "PauliI"] |> yields "PauliI" + given ["-p"; "PauliX"] |> yields "PauliX" + given ["-p"; "PauliY"] |> yields "PauliY" + given ["-p"; "PauliZ"] |> yields "PauliZ" + given ["-p"; "PauliW"] |> fails [] let ``Accepts Result`` () = - let assembly = testAssembly 9 - (assembly, [| "-r"; "Zero" |]) |> yields "Zero" - (assembly, [| "-r"; "zero" |]) |> yields "Zero" - (assembly, [| "-r"; "One" |]) |> yields "One" - (assembly, [| "-r"; "one" |]) |> yields "One" - (assembly, [| "-r"; "0" |]) |> yields "Zero" - (assembly, [| "-r"; "1" |]) |> yields "One" - (assembly, [| "-r"; "Two" |]) |> fails + let given = test 9 + given ["-r"; "Zero"] |> yields "Zero" + given ["-r"; "zero"] |> yields "Zero" + given ["-r"; "One"] |> yields "One" + given ["-r"; "one"] |> yields "One" + given ["-r"; "0"] |> yields "Zero" + given ["-r"; "1"] |> yields "One" + given ["-r"; "Two"] |> fails [] let ``Accepts Range`` () = - let assembly = testAssembly 10 - (assembly, [| "-r"; "0..0" |]) |> yields "0..1..0" - (assembly, [| "-r"; "0..1" |]) |> yields "0..1..1" - (assembly, [| "-r"; "0..2..10" |]) |> yields "0..2..10" - (assembly, [| "-r"; "0"; "..1" |]) |> yields "0..1..1" - (assembly, [| "-r"; "0.."; "1" |]) |> yields "0..1..1" - (assembly, [| "-r"; "0"; ".."; "1" |]) |> yields "0..1..1" - (assembly, [| "-r"; "0"; "..2"; "..10" |]) |> yields "0..2..10" - (assembly, [| "-r"; "0.."; "2"; "..10" |]) |> yields "0..2..10" - (assembly, [| "-r"; "0"; ".."; "2"; ".."; "10" |]) |> yields "0..2..10" - (assembly, [| "-r"; "0"; "1" |]) |> yields "0..1..1" - (assembly, [| "-r"; "0"; "2"; "10" |]) |> yields "0..2..10" - (assembly, [| "-r"; "0" |]) |> fails - (assembly, [| "-r"; "0.." |]) |> fails - (assembly, [| "-r"; "0..2.." |]) |> fails - (assembly, [| "-r"; "0..2..3.." |]) |> fails - (assembly, [| "-r"; "0..2..3..4" |]) |> fails - (assembly, [| "-r"; "0"; "1"; "2"; "3" |]) |> fails + let given = test 10 + given ["-r"; "0..0"] |> yields "0..1..0" + given ["-r"; "0..1"] |> yields "0..1..1" + given ["-r"; "0..2..10"] |> yields "0..2..10" + given ["-r"; "0"; "..1"] |> yields "0..1..1" + given ["-r"; "0.."; "1"] |> yields "0..1..1" + given ["-r"; "0"; ".."; "1"] |> yields "0..1..1" + given ["-r"; "0"; "..2"; "..10"] |> yields "0..2..10" + given ["-r"; "0.."; "2"; "..10"] |> yields "0..2..10" + given ["-r"; "0"; ".."; "2"; ".."; "10"] |> yields "0..2..10" + given ["-r"; "0"; "1"] |> yields "0..1..1" + given ["-r"; "0"; "2"; "10"] |> yields "0..2..10" + given ["-r"; "0"] |> fails + given ["-r"; "0.."] |> fails + given ["-r"; "0..2.."] |> fails + given ["-r"; "0..2..3.."] |> fails + given ["-r"; "0..2..3..4"] |> fails + given ["-r"; "0"; "1"; "2"; "3"] |> fails [] let ``Accepts String`` () = - (testAssembly 11, [| "-s"; "Hello, World!" |]) |> yields "Hello, World!" + let given = test 11 + given ["-s"; "Hello, World!"] |> yields "Hello, World!" [] let ``Accepts String array`` () = - let assembly = testAssembly 12 - (assembly, [| "--xs"; "foo" |]) |> yields "[foo]" - (assembly, [| "--xs"; "foo"; "bar" |]) |> yields "[foo,bar]" - (assembly, [| "--xs"; "foo bar"; "baz" |]) |> yields "[foo bar,baz]" - (assembly, [| "--xs"; "foo"; "bar"; "baz" |]) |> yields "[foo,bar,baz]" + let given = test 12 + 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]" [] let ``Accepts Unit`` () = - let assembly = testAssembly 13 - (assembly, [| "-u"; "()" |]) |> yields "" - (assembly, [| "-u"; "42" |]) |> fails + let given = test 13 + given ["-u"; "()"] |> yields "" + given ["-u"; "42"] |> fails From a3ba32dcec306d04bdb6884fe96da3394cd9f1c0 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 20 Apr 2020 18:58:06 -0700 Subject: [PATCH 51/70] Add help message test --- .../Circuits/EntryPointTests.qs | 23 +++++++++++++++ .../CsharpGeneration.Tests/EntryPointTests.fs | 28 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs b/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs index 81494cbe0b6..804843a3c39 100644 --- a/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs +++ b/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs @@ -121,3 +121,26 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { return u; } } + +// --- + +// +// Help +// + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + /// # Summary + /// This test checks that the entry point documentation appears correctly in the command line help message. + /// + /// # Input + /// ## n + /// A number. + /// + /// ## pauli + /// The name of a Pauli matrix. + /// + /// ## myCoolBool + /// A neat bit. + @EntryPoint() + operation Help(n : Int, pauli : Pauli, myCoolBool : Bool) : Unit { } +} diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index 4bdbe7bad65..a80526bf674 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -238,3 +238,31 @@ let ``Accepts Unit`` () = let given = test 13 given ["-u"; "()"] |> yields "" given ["-u"; "42"] |> fails + + +// Help + +[] +let ``Uses documentation`` () = + let name = Path.GetFileNameWithoutExtension (Assembly.GetEntryAssembly().Location) + let message = (name, name) ||> sprintf "%s: + This test checks that the entry point documentation appears correctly in the command line help message. + +Usage: + %s [options] [command] + +Options: + -n (REQUIRED) A number. + --pauli (REQUIRED) The name of a Pauli matrix. + --my-cool-bool (REQUIRED) A neat bit. + -s, --simulator The name of the simulator to use. + --version Show version information + -?, -h, --help Show help and usage information + +Commands: + simulate (default) Run the program using a local simulator." + + let given = test 14 + given ["--help"] |> yields message + given ["-h"] |> yields message + given ["-?"] |> yields message From 1778b9ef04b30ba1538ca864bba428f925e03aa7 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 20 Apr 2020 19:29:46 -0700 Subject: [PATCH 52/70] Add tests for name conversions and shadowing --- .../Circuits/EntryPointTests.qs | 53 +++++++++++++++++++ .../CsharpGeneration.Tests/EntryPointTests.fs | 42 +++++++++++++-- 2 files changed, 92 insertions(+), 3 deletions(-) diff --git a/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs b/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs index 804843a3c39..0e269a9d400 100644 --- a/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs +++ b/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs @@ -124,6 +124,59 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- +// +// Name Conversion +// + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation CamelCase(camelCaseName : String) : String { + return camelCaseName; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation SingleLetter(x : String) : String { + return x; + } +} + +// --- + +// +// Shadowing +// + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation ShadowSimulator(simulator : String) : String { + return simulator; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation ShadowS(s : String) : String { + return s; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation ShadowVersion(version : String) : String { + return version; + } +} + +// --- + // // Help // diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index a80526bf674..4924f21780d 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -100,11 +100,14 @@ let private run (assembly : Assembly) (args : string[]) = let main = driver.GetMethod("Main", BindingFlags.NonPublic ||| BindingFlags.Static) let previousCulture = CultureInfo.DefaultThreadCurrentCulture let previousOut = Console.Out + let previousError = Console.Error CultureInfo.DefaultThreadCurrentCulture <- CultureInfo ("en-US", false) use stream = new StringWriter () Console.SetOut stream + Console.SetError stream let exitCode = main.Invoke (null, [| args |]) :?> Task |> Async.AwaitTask |> Async.RunSynchronously + Console.SetError previousError Console.SetOut previousOut CultureInfo.DefaultThreadCurrentCulture <- previousCulture @@ -114,13 +117,13 @@ let private run (assembly : Assembly) (args : string[]) = /// output. let private yields expected (assembly, args) = let output, exitCode = run assembly args - Assert.Equal (0, exitCode) + Assert.True (0 = exitCode, sprintf "Expected exit code 0, but got %d with:\n\n%s" exitCode output) Assert.Equal (expected, output.TrimEnd ()) /// Asserts that running the entry point in the assembly with the given arguments fails. let private fails (assembly, args) = let output, exitCode = run assembly args - Assert.True (0 <> exitCode, "Succeeded unexpectedly:" + Environment.NewLine + output) + Assert.True (0 <> exitCode, "Expected non-zero exit code, but got 0 with:\n\n" + output) /// A tuple of the test assembly for the given test number, and the given argument string converted into an array. let test testNum = @@ -240,6 +243,39 @@ let ``Accepts Unit`` () = given ["-u"; "42"] |> fails +// Name Conversion + +[] +let ``Uses kebab-case`` () = + let given = test 14 + given ["--camel-case-name"; "foo"] |> yields "foo" + given ["--camelCaseName"; "foo"] |> fails + +[] +let ``Use single-dash short names`` () = + let given = test 15 + given ["-x"; "foo"] |> yields "foo" + given ["--x"; "foo"] |> fails + + +// Shadowing + +[] +let ``Shadows --simulator`` () = + let given = test 16 + given ["--simulator"; "foo"] |> yields "foo" + +[] +let ``Shadows -s`` () = + let given = test 17 + given ["-s"; "foo"] |> yields "foo" + +[] +let ``Shadows version`` () = + let given = test 18 + given ["--version"; "foo"] |> yields "foo" + + // Help [] @@ -262,7 +298,7 @@ Options: Commands: simulate (default) Run the program using a local simulator." - let given = test 14 + let given = test 19 given ["--help"] |> yields message given ["-h"] |> yields message given ["-?"] |> yields message From d8a9fd01d00aaa51ec4d194eb49253a0f0fc5b86 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 20 Apr 2020 19:52:00 -0700 Subject: [PATCH 53/70] Add tests for multiple options --- .../Circuits/EntryPointTests.qs | 22 ++++++++++ .../CsharpGeneration.Tests/EntryPointTests.fs | 40 ++++++++++++++++--- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs b/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs index 0e269a9d400..b1968cd96e6 100644 --- a/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs +++ b/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs @@ -124,6 +124,28 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- +// +// Multiple Options +// + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation TwoOptions(n : Int, b : Bool) : String { + return $"{n} {b}"; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + @EntryPoint() + operation ThreeOptions(n : Int, b : Bool, xs : String[]) : String { + return $"{n} {b} {xs}"; + } +} + +// --- + // // Name Conversion // diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index 4924f21780d..f24df874a61 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -243,17 +243,45 @@ let ``Accepts Unit`` () = given ["-u"; "42"] |> fails +// Multiple Options + +[] +let ``Accepts two options`` () = + let given = test 14 + given ["-n"; "7"; "-b"; "true"] |> yields "7 True" + given ["-b"; "true"; "-n"; "7"] |> yields "7 True" + +[] +let ``Accepts three options`` () = + let given = test 15 + 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]" + given ["-b"; "true"; "-n"; "7"; "--xs"; "foo"] |> yields "7 True [foo]" + +[] +let ``Requires all options`` () = + let given = test 15 + given ["-b"; "true"; "--xs"; "foo"] |> fails + given ["-n"; "7"; "--xs"; "foo"] |> fails + given ["-n"; "7"; "-b"; "true"] |> fails + given ["-n"; "7"] |> fails + given ["-b"; "true"] |> fails + given ["--xs"; "foo"] |> fails + given [] |> fails + + // Name Conversion [] let ``Uses kebab-case`` () = - let given = test 14 + let given = test 16 given ["--camel-case-name"; "foo"] |> yields "foo" given ["--camelCaseName"; "foo"] |> fails [] let ``Use single-dash short names`` () = - let given = test 15 + let given = test 17 given ["-x"; "foo"] |> yields "foo" given ["--x"; "foo"] |> fails @@ -262,17 +290,17 @@ let ``Use single-dash short names`` () = [] let ``Shadows --simulator`` () = - let given = test 16 + let given = test 18 given ["--simulator"; "foo"] |> yields "foo" [] let ``Shadows -s`` () = - let given = test 17 + let given = test 19 given ["-s"; "foo"] |> yields "foo" [] let ``Shadows version`` () = - let given = test 18 + let given = test 20 given ["--version"; "foo"] |> yields "foo" @@ -298,7 +326,7 @@ Options: Commands: simulate (default) Run the program using a local simulator." - let given = test 19 + let given = test 21 given ["--help"] |> yields message given ["-h"] |> yields message given ["-?"] |> yields message From d5819bae2d995411743c3f0c72422ad70efdf2eb Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 20 Apr 2020 20:16:31 -0700 Subject: [PATCH 54/70] Add simulator tests --- .../CsharpGeneration.Tests/EntryPointTests.fs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index f24df874a61..a0407b571b4 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -280,7 +280,7 @@ let ``Uses kebab-case`` () = given ["--camelCaseName"; "foo"] |> fails [] -let ``Use single-dash short names`` () = +let ``Uses single-dash short names`` () = let given = test 17 given ["-x"; "foo"] |> yields "foo" given ["--x"; "foo"] |> fails @@ -304,6 +304,37 @@ let ``Shadows version`` () = given ["--version"; "foo"] |> yields "foo" +// Simulators + +[] +let ``Supports QuantumSimulator`` () = + let given = test 3 + given ["--simulator"; "QuantumSimulator"] |> yields "Hello, World!" + +[] +let ``Supports ToffoliSimulator`` () = + let given = test 3 + given ["--simulator"; "ToffoliSimulator"] |> yields "Hello, World!" + +[] +let ``Supports ResourcesEstimator`` () = + let given = test 3 + given ["--simulator"; "ResourcesEstimator"] |> yields (("Metric \tSum " + " +CNOT \t0 +QubitClifford \t0 +R \t0 +Measure \t0 +T \t0 +Depth \t0 +Width \t0 +BorrowedWidth \t0").Replace("\r\n", "\n")) + +[] +let ``Rejects unknown simulator`` () = + let given = test 3 + given ["--simulator"; "FooSimulator"] |> fails + + // Help [] From f4b63575319da08eada3248d40d0811fa578859f Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 20 Apr 2020 20:27:39 -0700 Subject: [PATCH 55/70] Should be private --- src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index a0407b571b4..7a84a30002b 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -126,7 +126,7 @@ let private fails (assembly, args) = Assert.True (0 <> exitCode, "Expected non-zero exit code, but got 0 with:\n\n" + output) /// A tuple of the test assembly for the given test number, and the given argument string converted into an array. -let test testNum = +let private test testNum = let assembly = testAssembly testNum fun args -> assembly, Array.ofList args From db81d14fe1b403d538bea4deb0c6b63722ffc465 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 20 Apr 2020 20:42:42 -0700 Subject: [PATCH 56/70] More detailed error message for missing reference --- src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index 7a84a30002b..87e3aaa8ec0 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -59,9 +59,11 @@ let private generateCsharp testNum = /// The full path to a referenced assembly given its short name. let private referencedAssembly name = - (AppContext.GetData "TRUSTED_PLATFORM_ASSEMBLIES" :?> string).Split ';' - |> Seq.find (fun path -> String.Equals (Path.GetFileNameWithoutExtension path, name, - StringComparison.InvariantCultureIgnoreCase)) + let path = + (AppContext.GetData "TRUSTED_PLATFORM_ASSEMBLIES" :?> string).Split ';' + |> Seq.tryFind (fun path -> String.Equals (Path.GetFileNameWithoutExtension path, name, + StringComparison.InvariantCultureIgnoreCase)) + path |> Option.defaultWith (fun () -> failwith (sprintf "Missing reference to assembly '%s'." name)) /// Compiles the C# sources into an assembly. let private compileCsharp (sources : string seq) = From c7fcc707a9d6fb01c4ebfd50538f891cd8c8caa7 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 20 Apr 2020 21:11:53 -0700 Subject: [PATCH 57/70] Use platform-specific delimiter --- src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index 87e3aaa8ec0..4402b8288bb 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -59,8 +59,9 @@ let private generateCsharp testNum = /// The full path to a referenced assembly given its short name. let private referencedAssembly name = + let delimiter = if Environment.OSVersion.Platform = PlatformID.Win32NT then ';' else ':' let path = - (AppContext.GetData "TRUSTED_PLATFORM_ASSEMBLIES" :?> string).Split ';' + (AppContext.GetData "TRUSTED_PLATFORM_ASSEMBLIES" :?> string).Split delimiter |> Seq.tryFind (fun path -> String.Equals (Path.GetFileNameWithoutExtension path, name, StringComparison.InvariantCultureIgnoreCase)) path |> Option.defaultWith (fun () -> failwith (sprintf "Missing reference to assembly '%s'." name)) From 42f8fd345cb91df6c6917a0f7b1e8cbddfc80f6b Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 21 Apr 2020 11:44:01 -0700 Subject: [PATCH 58/70] Use default simulator when --simulator is shadowed --- .../CsharpGeneration.Tests/EntryPointTests.fs | 27 +++++---- src/Simulation/CsharpGeneration/EntryPoint.fs | 2 +- .../Resources/EntryPointDriver.cs | 57 ++++++++++++++----- 3 files changed, 62 insertions(+), 24 deletions(-) diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index 4402b8288bb..385cf9613ce 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -97,7 +97,8 @@ let private compileCsharp (sources : string seq) = /// The assembly for the given test case. let private testAssembly = generateCsharp >> compileCsharp -/// Runs the entry point driver in the assembly with the given command-line arguments, and returns the output. +/// Runs the entry point driver in the assembly with the given command-line arguments, and returns the output, errors, +/// and exit code. let private run (assembly : Assembly) (args : string[]) = let driver = assembly.GetType (EntryPoint.generatedNamespace testNamespace + ".Driver") let main = driver.GetMethod("Main", BindingFlags.NonPublic ||| BindingFlags.Static) @@ -106,27 +107,28 @@ let private run (assembly : Assembly) (args : string[]) = let previousError = Console.Error CultureInfo.DefaultThreadCurrentCulture <- CultureInfo ("en-US", false) - use stream = new StringWriter () - Console.SetOut stream - Console.SetError stream + use outStream = new StringWriter () + use errorStream = new StringWriter () + Console.SetOut outStream + Console.SetError errorStream let exitCode = main.Invoke (null, [| args |]) :?> Task |> Async.AwaitTask |> Async.RunSynchronously Console.SetError previousError Console.SetOut previousOut CultureInfo.DefaultThreadCurrentCulture <- previousCulture - stream.ToString (), exitCode + outStream.ToString (), errorStream.ToString (), exitCode /// Asserts that running the entry point in the assembly with the given arguments succeeds and yields the expected /// output. let private yields expected (assembly, args) = - let output, exitCode = run assembly args - Assert.True (0 = exitCode, sprintf "Expected exit code 0, but got %d with:\n\n%s" exitCode output) - Assert.Equal (expected, output.TrimEnd ()) + let out, error, exitCode = run assembly args + Assert.True (0 = exitCode, sprintf "Expected exit code 0, but got %d with:\n\n%s\n\n%s" exitCode out error) + Assert.Equal (expected, out.TrimEnd ()) /// Asserts that running the entry point in the assembly with the given arguments fails. let private fails (assembly, args) = - let output, exitCode = run assembly args - Assert.True (0 <> exitCode, "Expected non-zero exit code, but got 0 with:\n\n" + output) + let out, error, exitCode = run assembly args + Assert.True (0 <> exitCode, sprintf "Expected non-zero exit code, but got 0 with:\n\n%s\n\n%s" out error) /// A tuple of the test assembly for the given test number, and the given argument string converted into an array. let private test testNum = @@ -295,11 +297,16 @@ let ``Uses single-dash short names`` () = let ``Shadows --simulator`` () = let given = test 18 given ["--simulator"; "foo"] |> yields "foo" + given ["--simulator"; "ResourcesEstimator"] |> yields "ResourcesEstimator" + given ["-s"; "ResourcesEstimator"; "--simulator"; "foo"] |> fails + given ["-s"; "foo"] |> fails [] let ``Shadows -s`` () = let given = test 19 given ["-s"; "foo"] |> yields "foo" + given ["--simulator"; "ToffoliSimulator"; "-s"; "foo"] |> yields "foo" + given ["--simulator"; "bar"; "-s"; "foo"] |> fails [] let ``Shadows version`` () = diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index b4d42d88a2b..94bea39834b 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -37,7 +37,7 @@ let private constantsClass = ``:`` None ``,`` [] [``internal``; ``static``] ``{`` - [readonlyProperty "SimulatorOptionAliases" "System.Collections.Generic.IEnumerable" + [readonlyProperty "SimulatorOptions" "System.Collections.Generic.IEnumerable" (``new array`` (Some "") [``literal`` ("--" + fst CommandLineArguments.SimulatorOption) ``literal`` ("-" + snd CommandLineArguments.SimulatorOption)]) constant "QuantumSimulator" "string" (``literal`` AssemblyConstants.QuantumSimulator) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index b70e52b9a80..f7331229399 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -86,7 +86,7 @@ private static async Task Main(string[] args) { var simulate = new Command("simulate", "(default) Run the program using a local simulator."); TryCreateOption( - Constants.SimulatorOptionAliases, + Constants.SimulatorOptions, () => EntryPoint.DefaultSimulator, "The name of the simulator to use.").Then(option => { @@ -121,6 +121,7 @@ private static async Task Main(string[] args) /// 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: @@ -177,8 +178,43 @@ private static async Task DisplayEntryPointResult( } /// - /// Tries to create an option by ignoring aliases that are already in use by the entry point. If all of the - /// aliases are in use, the option is not created. + /// 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 if the alias is shadowed by an entry point option, and 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. @@ -186,16 +222,11 @@ private static async Task DisplayEntryPointResult( /// The option's description. /// The result of trying to create the option. private static Result> TryCreateOption( - IEnumerable aliases, Func getDefaultValue, string description = null) - { - static bool isAliasAvailable(string alias) => - !EntryPoint.Options.SelectMany(option => option.RawAliases).Contains(alias); - - var validAliases = aliases.Where(isAliasAvailable); - return validAliases.Any() - ? Result>.Success(new Option(validAliases.ToArray(), getDefaultValue, description)) - : Result>.Failure(); - } + 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. From 92a1f1c139ad4d0318f410bc81ce643a2d43b1bc Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 21 Apr 2020 13:23:50 -0700 Subject: [PATCH 59/70] Normalize whitespace in tests --- .../CsharpGeneration.Tests/EntryPointTests.fs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index 385cf9613ce..77c707322b9 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -8,6 +8,7 @@ open System.Collections.Immutable open System.Globalization open System.IO open System.Reflection +open System.Text.RegularExpressions open System.Threading.Tasks open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.CSharp @@ -121,9 +122,10 @@ let private run (assembly : Assembly) (args : string[]) = /// Asserts that running the entry point in the assembly with the given arguments succeeds and yields the expected /// output. let private yields expected (assembly, args) = + let normalize text = Regex.Replace(text, @"\s+", " ").Trim() let out, error, exitCode = run assembly args Assert.True (0 = exitCode, sprintf "Expected exit code 0, but got %d with:\n\n%s\n\n%s" exitCode out error) - Assert.Equal (expected, out.TrimEnd ()) + Assert.Equal (normalize expected, normalize out) /// Asserts that running the entry point in the assembly with the given arguments fails. let private fails (assembly, args) = @@ -329,15 +331,15 @@ let ``Supports ToffoliSimulator`` () = [] let ``Supports ResourcesEstimator`` () = let given = test 3 - given ["--simulator"; "ResourcesEstimator"] |> yields (("Metric \tSum " + " -CNOT \t0 -QubitClifford \t0 -R \t0 -Measure \t0 -T \t0 -Depth \t0 -Width \t0 -BorrowedWidth \t0").Replace("\r\n", "\n")) + given ["--simulator"; "ResourcesEstimator"] |> yields "Metric Sum +CNOT 0 +QubitClifford 0 +R 0 +Measure 0 +T 0 +Depth 0 +Width 0 +BorrowedWidth 0" [] let ``Rejects unknown simulator`` () = From d56c8371e6b14844cc3090237d20f69a81859301 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 21 Apr 2020 14:19:54 -0700 Subject: [PATCH 60/70] Update DefaultIfShadowed summary --- src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index f7331229399..276416690a9 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -187,8 +187,8 @@ private static bool IsAliasAvailable(string alias) => !EntryPoint.Options.SelectMany(option => option.RawAliases).Contains(alias); /// - /// Returns the default value if the alias is shadowed by an entry point option, and the original value - /// otherwise. + /// 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. From fcd1c2005468403b72558fefae0da9117711895e Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 21 Apr 2020 14:31:52 -0700 Subject: [PATCH 61/70] Add DefaultSimulator tests --- .../CsharpGeneration.Tests/EntryPointTests.fs | 61 ++++++++++++++----- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index 77c707322b9..452092cad81 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -15,6 +15,7 @@ open Microsoft.CodeAnalysis.CSharp open Microsoft.Quantum.QsCompiler.CompilationBuilder open Microsoft.Quantum.QsCompiler.CsharpGeneration open Microsoft.Quantum.QsCompiler.DataTypes +open Microsoft.Quantum.QsCompiler.ReservedKeywords open Microsoft.VisualStudio.LanguageServer.Protocol open Xunit @@ -48,10 +49,14 @@ let private compileQsharp source = Assert.Empty errors compilation.BuiltCompilation.Namespaces, compilation.BuiltCompilation.EntryPoints -/// Generates C# source code for the given test case number. -let private generateCsharp testNum = +/// Generates C# source code for the given test case number and default simulator. +let private generateCsharp (testNum, defaultSimulator) = let syntaxTree, entryPoints = compileQsharp tests.[testNum - 1] - let context = CodegenContext.Create syntaxTree + let assemblyConstants = + match defaultSimulator with + | Some simulator -> ImmutableDictionary.Empty.Add (AssemblyConstants.DefaultSimulator, simulator) + | None -> ImmutableDictionary.Empty + let context = CodegenContext.Create (syntaxTree, assemblyConstants) let entryPoint = context.allCallables.[Seq.exactlyOne entryPoints] [ SimulationCode.generate (NonNullable<_>.New testFile) context @@ -132,11 +137,19 @@ let private fails (assembly, args) = let out, error, exitCode = run assembly args Assert.True (0 <> exitCode, sprintf "Expected non-zero exit code, but got 0 with:\n\n%s\n\n%s" out error) -/// A tuple of the test assembly for the given test number, and the given argument string converted into an array. +/// A tuple of the test assembly and arguments using the standard default simulator. The tuple can be passed to yields +/// or fails. let private test testNum = - let assembly = testAssembly testNum + let assembly = testAssembly (testNum, None) fun args -> assembly, Array.ofList args +/// A tuple of the test assembly and arguments using the given default simulator. The tuple can be passed to yields or +/// fails. +let private testWith testNum defaultSimulator = + let assembly = testAssembly (testNum, Some defaultSimulator) + fun args -> assembly, Array.ofList args + + // No Option [] @@ -318,6 +331,17 @@ let ``Shadows version`` () = // Simulators +// The expected output from the resources estimator. +let private resourceSummary = "Metric Sum +CNOT 0 +QubitClifford 0 +R 0 +Measure 0 +T 0 +Depth 0 +Width 0 +BorrowedWidth 0" + [] let ``Supports QuantumSimulator`` () = let given = test 3 @@ -331,21 +355,30 @@ let ``Supports ToffoliSimulator`` () = [] let ``Supports ResourcesEstimator`` () = let given = test 3 - given ["--simulator"; "ResourcesEstimator"] |> yields "Metric Sum -CNOT 0 -QubitClifford 0 -R 0 -Measure 0 -T 0 -Depth 0 -Width 0 -BorrowedWidth 0" + given ["--simulator"; "ResourcesEstimator"] |> yields resourceSummary [] let ``Rejects unknown simulator`` () = let given = test 3 given ["--simulator"; "FooSimulator"] |> fails +[] +let ``Supports default standard simulator`` () = + let given = testWith 3 "ResourcesEstimator" + given [] |> yields resourceSummary + given ["--simulator"; "QuantumSimulator"] |> yields "Hello, World!" + +[] +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 3 "Microsoft.Quantum.Simulation.Simulators.ToffoliSimulator" + given [] |> yields "Hello, World!" + given ["--simulator"; "Microsoft.Quantum.Simulation.Simulators.ToffoliSimulator"] |> yields "Hello, World!" + given ["--simulator"; "QuantumSimulator"] |> yields "Hello, World!" + given ["--simulator"; "ResourcesEstimator"] |> yields resourceSummary + given ["--simulator"; "Microsoft.Quantum.Simulation.Simulators.QuantumSimulator"] |> fails + // Help From 960cc7174133e00bd502a32427c1370c87cbb10f Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 21 Apr 2020 14:40:22 -0700 Subject: [PATCH 62/70] Use AssemblyConstants for simulator names --- .../CsharpGeneration.Tests/EntryPointTests.fs | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index 452092cad81..660856f35cd 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -12,12 +12,14 @@ open System.Text.RegularExpressions open System.Threading.Tasks open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.CSharp +open Microsoft.VisualStudio.LanguageServer.Protocol +open Xunit + open Microsoft.Quantum.QsCompiler.CompilationBuilder open Microsoft.Quantum.QsCompiler.CsharpGeneration open Microsoft.Quantum.QsCompiler.DataTypes open Microsoft.Quantum.QsCompiler.ReservedKeywords -open Microsoft.VisualStudio.LanguageServer.Protocol -open Xunit +open Microsoft.Quantum.Simulation.Simulators /// The path to the Q# file that provides the Microsoft.Quantum.Core namespace. @@ -312,15 +314,15 @@ let ``Uses single-dash short names`` () = let ``Shadows --simulator`` () = let given = test 18 given ["--simulator"; "foo"] |> yields "foo" - given ["--simulator"; "ResourcesEstimator"] |> yields "ResourcesEstimator" - given ["-s"; "ResourcesEstimator"; "--simulator"; "foo"] |> fails + given ["--simulator"; AssemblyConstants.ResourcesEstimator] |> yields AssemblyConstants.ResourcesEstimator + given ["-s"; AssemblyConstants.ResourcesEstimator; "--simulator"; "foo"] |> fails given ["-s"; "foo"] |> fails [] let ``Shadows -s`` () = let given = test 19 given ["-s"; "foo"] |> yields "foo" - given ["--simulator"; "ToffoliSimulator"; "-s"; "foo"] |> yields "foo" + given ["--simulator"; AssemblyConstants.ToffoliSimulator; "-s"; "foo"] |> yields "foo" given ["--simulator"; "bar"; "-s"; "foo"] |> fails [] @@ -345,17 +347,17 @@ BorrowedWidth 0" [] let ``Supports QuantumSimulator`` () = let given = test 3 - given ["--simulator"; "QuantumSimulator"] |> yields "Hello, World!" + given ["--simulator"; AssemblyConstants.QuantumSimulator] |> yields "Hello, World!" [] let ``Supports ToffoliSimulator`` () = let given = test 3 - given ["--simulator"; "ToffoliSimulator"] |> yields "Hello, World!" + given ["--simulator"; AssemblyConstants.ToffoliSimulator] |> yields "Hello, World!" [] let ``Supports ResourcesEstimator`` () = let given = test 3 - given ["--simulator"; "ResourcesEstimator"] |> yields resourceSummary + given ["--simulator"; AssemblyConstants.ResourcesEstimator] |> yields resourceSummary [] let ``Rejects unknown simulator`` () = @@ -364,20 +366,20 @@ let ``Rejects unknown simulator`` () = [] let ``Supports default standard simulator`` () = - let given = testWith 3 "ResourcesEstimator" + let given = testWith 3 AssemblyConstants.ResourcesEstimator given [] |> yields resourceSummary - given ["--simulator"; "QuantumSimulator"] |> yields "Hello, World!" + given ["--simulator"; AssemblyConstants.QuantumSimulator] |> yields "Hello, World!" [] 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 3 "Microsoft.Quantum.Simulation.Simulators.ToffoliSimulator" + let given = testWith 3 typeof.FullName given [] |> yields "Hello, World!" - given ["--simulator"; "Microsoft.Quantum.Simulation.Simulators.ToffoliSimulator"] |> yields "Hello, World!" - given ["--simulator"; "QuantumSimulator"] |> yields "Hello, World!" - given ["--simulator"; "ResourcesEstimator"] |> yields resourceSummary - given ["--simulator"; "Microsoft.Quantum.Simulation.Simulators.QuantumSimulator"] |> fails + given ["--simulator"; typeof.FullName] |> yields "Hello, World!" + given ["--simulator"; AssemblyConstants.QuantumSimulator] |> yields "Hello, World!" + given ["--simulator"; AssemblyConstants.ResourcesEstimator] |> yields resourceSummary + given ["--simulator"; typeof.FullName] |> fails // Help From b006c3bda0415552080585b28e7e0cbc155eae42 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 21 Apr 2020 14:58:09 -0700 Subject: [PATCH 63/70] Use H gate to test for ToffoliSimulator --- .../Circuits/EntryPointTests.qs | 26 ++++++++++ .../CsharpGeneration.Tests/EntryPointTests.fs | 52 +++++++++++-------- 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs b/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs index b1968cd96e6..54efe03736d 100644 --- a/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs +++ b/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs @@ -199,6 +199,32 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- +// +// Simulators +// + +namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { + open Microsoft.Quantum.Intrinsic; + + @EntryPoint() + operation XOrH(useH : Bool) : String { + using (q = Qubit()) { + if (useH) { + H(q); + } else { + X(q); + } + + if (M(q) == One) { + X(q); + } + } + return "Hello, World!"; + } +} + +// --- + // // Help // diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index 660856f35cd..b9c5b559233 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -25,6 +25,9 @@ open Microsoft.Quantum.Simulation.Simulators /// The path to the Q# file that provides the Microsoft.Quantum.Core namespace. let private coreFile = Path.Combine ("Circuits", "Core.qs") |> Path.GetFullPath +/// The path to the Q# file that provides the Microsoft.Quantum.Intrinsic namespace. +let private intrinsicFile = Path.Combine ("Circuits", "Intrinsic.qs") |> Path.GetFullPath + /// The path to the Q# file that contains the test cases. let private testFile = Path.Combine ("Circuits", "EntryPointTests.qs") |> Path.GetFullPath @@ -42,6 +45,7 @@ let private compileQsharp source = use compilationManager = new CompilationUnitManager (isExecutable = true) let fileManagers = ImmutableHashSet.Create (fileManager coreFile (File.ReadAllText coreFile), + fileManager intrinsicFile (File.ReadAllText intrinsicFile), fileManager testFile source) compilationManager.AddOrUpdateSourceFilesAsync fileManagers |> ignore let compilation = compilationManager.Build () @@ -336,50 +340,56 @@ let ``Shadows version`` () = // The expected output from the resources estimator. let private resourceSummary = "Metric Sum CNOT 0 -QubitClifford 0 +QubitClifford 1 R 0 -Measure 0 +Measure 1 T 0 Depth 0 -Width 0 +Width 1 BorrowedWidth 0" [] let ``Supports QuantumSimulator`` () = - let given = test 3 - given ["--simulator"; AssemblyConstants.QuantumSimulator] |> yields "Hello, World!" + let given = test 21 + 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 3 - given ["--simulator"; AssemblyConstants.ToffoliSimulator] |> yields "Hello, World!" + let given = test 21 + given ["--simulator"; AssemblyConstants.ToffoliSimulator; "--use-h"; "false"] |> yields "Hello, World!" + given ["--simulator"; AssemblyConstants.ToffoliSimulator; "--use-h"; "true"] |> fails [] let ``Supports ResourcesEstimator`` () = - let given = test 3 - given ["--simulator"; AssemblyConstants.ResourcesEstimator] |> yields resourceSummary + let given = test 21 + 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 3 - given ["--simulator"; "FooSimulator"] |> fails + let given = test 21 + given ["--simulator"; "FooSimulator"; "--use-h"; "false"] |> fails [] let ``Supports default standard simulator`` () = - let given = testWith 3 AssemblyConstants.ResourcesEstimator - given [] |> yields resourceSummary - given ["--simulator"; AssemblyConstants.QuantumSimulator] |> yields "Hello, World!" + let given = testWith 21 AssemblyConstants.ResourcesEstimator + given ["--use-h"; "false"] |> yields resourceSummary + given ["--simulator"; AssemblyConstants.QuantumSimulator; "--use-h"; "false"] |> yields "Hello, World!" [] 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 3 typeof.FullName - given [] |> yields "Hello, World!" - given ["--simulator"; typeof.FullName] |> yields "Hello, World!" - given ["--simulator"; AssemblyConstants.QuantumSimulator] |> yields "Hello, World!" - given ["--simulator"; AssemblyConstants.ResourcesEstimator] |> yields resourceSummary - given ["--simulator"; typeof.FullName] |> fails + let given = testWith 21 typeof.FullName + given ["--use-h"; "false"] |> yields "Hello, World!" + given ["--use-h"; "true"] |> fails + given ["--simulator"; typeof.FullName; "--use-h"; "false"] |> yields "Hello, World!" + given ["--simulator"; typeof.FullName; "--use-h"; "true"] |> fails + given ["--simulator"; AssemblyConstants.QuantumSimulator; "--use-h"; "false"] |> yields "Hello, World!" + given ["--simulator"; AssemblyConstants.QuantumSimulator; "--use-h"; "true"] |> yields "Hello, World!" + given ["--simulator"; AssemblyConstants.ResourcesEstimator; "--use-h"; "false"] |> yields resourceSummary + given ["--simulator"; typeof.FullName; "--use-h"; "false"] |> fails // Help @@ -404,7 +414,7 @@ Options: Commands: simulate (default) Run the program using a local simulator." - let given = test 21 + let given = test 22 given ["--help"] |> yields message given ["-h"] |> yields message given ["-?"] |> yields message From a26a63733adc9acd615df728d2de9e5c7f1a98aa Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 21 Apr 2020 15:41:22 -0700 Subject: [PATCH 64/70] Use try/finally for console redirects and current culture --- .../CsharpGeneration.Tests/EntryPointTests.fs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index b9c5b559233..2bb399a754d 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -121,14 +121,15 @@ let private run (assembly : Assembly) (args : string[]) = CultureInfo.DefaultThreadCurrentCulture <- CultureInfo ("en-US", false) use outStream = new StringWriter () use errorStream = new StringWriter () - Console.SetOut outStream - Console.SetError errorStream - let exitCode = main.Invoke (null, [| args |]) :?> Task |> Async.AwaitTask |> Async.RunSynchronously - Console.SetError previousError - Console.SetOut previousOut - CultureInfo.DefaultThreadCurrentCulture <- previousCulture - - outStream.ToString (), errorStream.ToString (), exitCode + try + Console.SetOut outStream + Console.SetError errorStream + let exitCode = main.Invoke (null, [| args |]) :?> Task |> Async.AwaitTask |> Async.RunSynchronously + outStream.ToString (), errorStream.ToString (), exitCode + finally + Console.SetError previousError + Console.SetOut previousOut + CultureInfo.DefaultThreadCurrentCulture <- previousCulture /// Asserts that running the entry point in the assembly with the given arguments succeeds and yields the expected /// output. From 52c2873b7bf01ddaaabbc81fea36a990d309cf92 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 21 Apr 2020 15:48:29 -0700 Subject: [PATCH 65/70] Add missing dashes in test name --- src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index 2bb399a754d..4415d6ba5ea 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -331,7 +331,7 @@ let ``Shadows -s`` () = given ["--simulator"; "bar"; "-s"; "foo"] |> fails [] -let ``Shadows version`` () = +let ``Shadows --version`` () = let given = test 20 given ["--version"; "foo"] |> yields "foo" From 6f426a8a8dcac3942cc66d0326fa766a5b760e67 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 21 Apr 2020 21:47:27 -0700 Subject: [PATCH 66/70] Better way to write the testAssembly function --- .../CsharpGeneration.Tests/EntryPointTests.fs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index 4415d6ba5ea..f2344ec3f4f 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -19,6 +19,7 @@ open Microsoft.Quantum.QsCompiler.CompilationBuilder open Microsoft.Quantum.QsCompiler.CsharpGeneration open Microsoft.Quantum.QsCompiler.DataTypes open Microsoft.Quantum.QsCompiler.ReservedKeywords +open Microsoft.Quantum.QsCompiler.SyntaxTree open Microsoft.Quantum.Simulation.Simulators @@ -34,8 +35,11 @@ let private testFile = Path.Combine ("Circuits", "EntryPointTests.qs") |> Path.G /// The namespace used for the test cases. let private testNamespace = "Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint" -/// The test cases. -let private tests = File.ReadAllText testFile |> fun text -> text.Split "// ---" +/// The test case for the given test number. +let private testCase = + File.ReadAllText testFile + |> fun text -> text.Split "// ---" + |> fun cases num -> cases.[num - 1] /// Compiles Q# source code into a syntax tree with the list of entry points names. let private compileQsharp source = @@ -56,8 +60,7 @@ let private compileQsharp source = compilation.BuiltCompilation.Namespaces, compilation.BuiltCompilation.EntryPoints /// Generates C# source code for the given test case number and default simulator. -let private generateCsharp (testNum, defaultSimulator) = - let syntaxTree, entryPoints = compileQsharp tests.[testNum - 1] +let private generateCsharp defaultSimulator (syntaxTree : QsNamespace seq, entryPoints) = let assemblyConstants = match defaultSimulator with | Some simulator -> ImmutableDictionary.Empty.Add (AssemblyConstants.DefaultSimulator, simulator) @@ -106,8 +109,13 @@ let private compileCsharp (sources : string seq) = Assert.Equal (0L, stream.Seek (0L, SeekOrigin.Begin)) Assembly.Load (stream.ToArray ()) -/// The assembly for the given test case. -let private testAssembly = generateCsharp >> compileCsharp +/// The assembly for the given test case and default simulator. +let private testAssembly testNum defaultSimulator = + testNum + |> testCase + |> compileQsharp + |> generateCsharp defaultSimulator + |> compileCsharp /// Runs the entry point driver in the assembly with the given command-line arguments, and returns the output, errors, /// and exit code. @@ -147,13 +155,13 @@ let private fails (assembly, args) = /// A tuple of the test assembly and arguments using the standard default simulator. The tuple can be passed to yields /// or fails. let private test testNum = - let assembly = testAssembly (testNum, None) + let assembly = testAssembly testNum None fun args -> assembly, Array.ofList args /// A tuple of the test assembly and arguments using the given default simulator. The tuple can be passed to yields or /// fails. let private testWith testNum defaultSimulator = - let assembly = testAssembly (testNum, Some defaultSimulator) + let assembly = testAssembly testNum (Some defaultSimulator) fun args -> assembly, Array.ofList args From c938ae88f832093f228a89a5455e7be791cfba3e Mon Sep 17 00:00:00 2001 From: bettinaheim <34236215+bettinaheim@users.noreply.github.com> Date: Wed, 22 Apr 2020 14:06:59 -0700 Subject: [PATCH 67/70] adding an execution test (#174) --- .gitignore | 1 + Simulation.sln | 22 ++++++++ src/Simulation/Simulators.Tests/CoreTests.cs | 54 ++++++++++++------- .../TestProjects/QsharpExe/Main.qs | 16 ++++++ .../TestProjects/QsharpExe/QsharpExe.csproj | 40 ++++++++++++++ ...osoft.Quantum.Simulation.Simulators.csproj | 24 ++++++++- 6 files changed, 137 insertions(+), 20 deletions(-) create mode 100644 src/Simulation/Simulators.Tests/TestProjects/QsharpExe/Main.qs create mode 100644 src/Simulation/Simulators.Tests/TestProjects/QsharpExe/QsharpExe.csproj diff --git a/.gitignore b/.gitignore index 9f77ea53d20..7cffa506f2b 100644 --- a/.gitignore +++ b/.gitignore @@ -329,3 +329,4 @@ ASALocalRun/ # MFractors (Xamarin productivity tool) working folder .mfractor/ +/src/Simulation/Simulators.Tests/TestProjects/QsharpExe/built diff --git a/Simulation.sln b/Simulation.sln index ce58ef86a2b..c5a58260186 100644 --- a/Simulation.sln +++ b/Simulation.sln @@ -35,6 +35,10 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Tests.RoslynWrapper", "src\ EndProject Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Microsoft.Quantum.CsharpGeneration", "src\Simulation\CsharpGeneration\Microsoft.Quantum.CsharpGeneration.fsproj", "{B96E97F4-2DC8-45AC-ADF5-861D0D3073FC}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestProjects", "TestProjects", "{09C842CB-930C-4C7D-AD5F-E30DE4A55820}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QsharpExe", "src\Simulation\Simulators.Tests\TestProjects\QsharpExe\QsharpExe.csproj", "{2F5796A7-4AF8-4B78-928A-0A3A80752F9D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -239,6 +243,22 @@ Global {B96E97F4-2DC8-45AC-ADF5-861D0D3073FC}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU {B96E97F4-2DC8-45AC-ADF5-861D0D3073FC}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU {B96E97F4-2DC8-45AC-ADF5-861D0D3073FC}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {2F5796A7-4AF8-4B78-928A-0A3A80752F9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F5796A7-4AF8-4B78-928A-0A3A80752F9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F5796A7-4AF8-4B78-928A-0A3A80752F9D}.Debug|x64.ActiveCfg = Debug|Any CPU + {2F5796A7-4AF8-4B78-928A-0A3A80752F9D}.Debug|x64.Build.0 = Debug|Any CPU + {2F5796A7-4AF8-4B78-928A-0A3A80752F9D}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {2F5796A7-4AF8-4B78-928A-0A3A80752F9D}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {2F5796A7-4AF8-4B78-928A-0A3A80752F9D}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {2F5796A7-4AF8-4B78-928A-0A3A80752F9D}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {2F5796A7-4AF8-4B78-928A-0A3A80752F9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F5796A7-4AF8-4B78-928A-0A3A80752F9D}.Release|Any CPU.Build.0 = Release|Any CPU + {2F5796A7-4AF8-4B78-928A-0A3A80752F9D}.Release|x64.ActiveCfg = Release|Any CPU + {2F5796A7-4AF8-4B78-928A-0A3A80752F9D}.Release|x64.Build.0 = Release|Any CPU + {2F5796A7-4AF8-4B78-928A-0A3A80752F9D}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {2F5796A7-4AF8-4B78-928A-0A3A80752F9D}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {2F5796A7-4AF8-4B78-928A-0A3A80752F9D}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {2F5796A7-4AF8-4B78-928A-0A3A80752F9D}.RelWithDebInfo|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -256,6 +276,8 @@ Global {618FBF9D-4EF3-435D-9728-81C726236668} = {A567C185-A429-418B-AFDE-6F1785BA4A77} {48206BD6-48DD-4442-A395-3A6594E4C9C6} = {A567C185-A429-418B-AFDE-6F1785BA4A77} {B96E97F4-2DC8-45AC-ADF5-861D0D3073FC} = {A567C185-A429-418B-AFDE-6F1785BA4A77} + {09C842CB-930C-4C7D-AD5F-E30DE4A55820} = {34D419E9-CCF1-4E48-9FA4-3AD4B86BEEB4} + {2F5796A7-4AF8-4B78-928A-0A3A80752F9D} = {09C842CB-930C-4C7D-AD5F-E30DE4A55820} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {929C0464-86D8-4F70-8835-0A5EAF930821} diff --git a/src/Simulation/Simulators.Tests/CoreTests.cs b/src/Simulation/Simulators.Tests/CoreTests.cs index 15e45c68ccc..ba2c9e9e5fa 100644 --- a/src/Simulation/Simulators.Tests/CoreTests.cs +++ b/src/Simulation/Simulators.Tests/CoreTests.cs @@ -1,9 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Collections.Generic; using System.IO; - +using System.Reflection; +using System.Text; +using Microsoft.Quantum.QsCompiler; using Microsoft.Quantum.Simulation.Common; using Microsoft.Quantum.Simulation.Core; using Microsoft.Quantum.Simulation.Simulators.QCTraceSimulators; @@ -14,8 +17,6 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests { - using Helper = Microsoft.Quantum.Simulation.Simulators.Tests.OperationsTestHelper; - public class CoreTests { private readonly ITestOutputHelper output; @@ -25,10 +26,27 @@ public CoreTests(ITestOutputHelper output) this.output = output; } + [Fact] + public void BasicExecution() + { + var asmPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + var exe = Path.Combine(asmPath, "TestExe", "QsharpExe.exe"); + + ProcessRunner.Run(exe, "", out var _, out StringBuilder error, out int exitCode, out Exception ex); + Assert.Null(ex); + Assert.Equal(1, exitCode); + Assert.Contains("NotImplementedException", error.ToString()); + + ProcessRunner.Run(exe, "--simulator QuantumSimulator", out var _, out error, out exitCode, out ex); + Assert.Null(ex); + Assert.Equal(0, exitCode); + Assert.Empty(error.ToString().Trim()); + } + [Fact] public void Borrowing() { - Helper.RunWithMultipleSimulators((s) => + OperationsTestHelper.RunWithMultipleSimulators((s) => { var tracker = new StartTracker(s); @@ -44,18 +62,18 @@ public void Borrowing() BorrowingTest.Run(s).Wait(); - var tracer = Helper.GetTracer<(long, Qubit)>(s); + var tracer = OperationsTestHelper.GetTracer<(long, Qubit)>(s); - var testOne = new System.Action((callsCount, variant, info) => + var testOne = new Action((int callsCount, OperationFunctor variant, (int, Qubit) info) => { var (available, q) = info; Assert.Equal(callsCount, tracer.Log.GetNumberOfCalls(variant, (available, q))); }); - var testOneBody = new System.Action((callsCount, info) => testOne(callsCount, OperationFunctor.Body, info)); - var testOneAdjoint = new System.Action((callsCount, info) => testOne(callsCount, OperationFunctor.Adjoint, info)); - var testOneCtrl = new System.Action((callsCount, info) => testOne(callsCount, OperationFunctor.Controlled, info)); - var testOneCtrlAdj = new System.Action((callsCount, info) => testOne(callsCount, OperationFunctor.ControlledAdjoint, info)); + var testOneBody = new Action((callsCount, info) => testOne(callsCount, OperationFunctor.Body, info)); + var testOneAdjoint = new Action((callsCount, info) => testOne(callsCount, OperationFunctor.Adjoint, info)); + var testOneCtrl = new Action((callsCount, info) => testOne(callsCount, OperationFunctor.Controlled, info)); + var testOneCtrlAdj = new Action((callsCount, info) => testOne(callsCount, OperationFunctor.ControlledAdjoint, info)); testOneBody(6, (0, q5)); testOneBody(6, (0, q6)); @@ -180,7 +198,7 @@ void RunOne(IOperationFactory s) Assert.False(File.Exists("()")); } - Helper.RunWithMultipleSimulators((s) => RunOne(s as IOperationFactory)); + OperationsTestHelper.RunWithMultipleSimulators((s) => RunOne(s as IOperationFactory)); RunOne(new QCTraceSimulator()); RunOne(new ResourcesEstimator()); RunOne(new QuantumSimulator()); @@ -215,7 +233,7 @@ void RunOne(IOperationFactory s) } } - Helper.RunWithMultipleSimulators((s) => RunOne(s as IOperationFactory)); + OperationsTestHelper.RunWithMultipleSimulators((s) => RunOne(s as IOperationFactory)); RunOne(new QCTraceSimulator()); RunOne(new ResourcesEstimator()); } @@ -223,9 +241,9 @@ void RunOne(IOperationFactory s) [Fact] public void ZeroQubits() { - Helper.RunWithMultipleSimulators((s) => + OperationsTestHelper.RunWithMultipleSimulators((s) => { - var tracer = Helper.GetTracer(s); + var tracer = OperationsTestHelper.GetTracer(s); ZeroQubitsTest.Run(s).Wait(); @@ -236,7 +254,7 @@ public void ZeroQubits() [Fact] public void InterpolatedStrings() { - Helper.RunWithMultipleSimulators((s) => + OperationsTestHelper.RunWithMultipleSimulators((s) => { Circuits.InterpolatedStringTest.Run(s).Wait(); // Throws if it doesn't succeed }); @@ -245,7 +263,7 @@ public void InterpolatedStrings() [Fact] public void RandomOperation() { - Helper.RunWithMultipleSimulators((s) => + OperationsTestHelper.RunWithMultipleSimulators((s) => { Circuits.RandomOperationTest.Run(s).Wait(); // Throws if it doesn't succeed }); @@ -254,7 +272,7 @@ public void RandomOperation() [Fact] public void BigInts() { - Helper.RunWithMultipleSimulators((s) => + OperationsTestHelper.RunWithMultipleSimulators((s) => { Circuits.BigIntTest.Run(s).Wait(); // Throws if it doesn't succeed }); @@ -282,6 +300,6 @@ public void CatchFail() [Fact] public void InternalCallables() => - Helper.RunWithMultipleSimulators(s => Circuits.InternalCallablesTest.Run(s).Wait()); + OperationsTestHelper.RunWithMultipleSimulators(s => Circuits.InternalCallablesTest.Run(s).Wait()); } } diff --git a/src/Simulation/Simulators.Tests/TestProjects/QsharpExe/Main.qs b/src/Simulation/Simulators.Tests/TestProjects/QsharpExe/Main.qs new file mode 100644 index 00000000000..41a087bad12 --- /dev/null +++ b/src/Simulation/Simulators.Tests/TestProjects/QsharpExe/Main.qs @@ -0,0 +1,16 @@ +namespace QsharpExe { + + open Microsoft.Quantum.Intrinsic; + + @EntryPoint() + operation Main () : Result { + using (q = Qubit()) { + H(q); + let res = M(q); + if (res == One) { + X(q); + } + return res; + } + } +} diff --git a/src/Simulation/Simulators.Tests/TestProjects/QsharpExe/QsharpExe.csproj b/src/Simulation/Simulators.Tests/TestProjects/QsharpExe/QsharpExe.csproj new file mode 100644 index 00000000000..25e28abe68d --- /dev/null +++ b/src/Simulation/Simulators.Tests/TestProjects/QsharpExe/QsharpExe.csproj @@ -0,0 +1,40 @@ + + + + Exe + netcoreapp3.1 + + false + false + ToffoliSimulator + + + + + + + + + + + + + + + + + <_ExeDir>$(MSBuildThisFileDirectory)built + + + + + + + + <_ExeFiles Include="$(OutputPath)*" /> + + + + + + diff --git a/src/Simulation/Simulators.Tests/Tests.Microsoft.Quantum.Simulation.Simulators.csproj b/src/Simulation/Simulators.Tests/Tests.Microsoft.Quantum.Simulation.Simulators.csproj index b489a310a22..6e8583c9975 100644 --- a/src/Simulation/Simulators.Tests/Tests.Microsoft.Quantum.Simulation.Simulators.csproj +++ b/src/Simulation/Simulators.Tests/Tests.Microsoft.Quantum.Simulation.Simulators.csproj @@ -11,9 +11,18 @@ false + + + + + + - + + + false + @@ -24,7 +33,18 @@ - + + + + + <_ExeDir>$(MSBuildThisFileDirectory)TestProjects\QsharpExe\built\ + + + <_ExeFiles Include="$(_ExeDir)*" /> + + + + From 7ed88f06de7c116e294cd950391f32ecb7ba2c90 Mon Sep 17 00:00:00 2001 From: Bettina Heim Date: Wed, 22 Apr 2020 14:39:48 -0700 Subject: [PATCH 68/70] forgot to use dll instead of exe --- src/Simulation/Simulators.Tests/CoreTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Simulation/Simulators.Tests/CoreTests.cs b/src/Simulation/Simulators.Tests/CoreTests.cs index ba2c9e9e5fa..981c4776169 100644 --- a/src/Simulation/Simulators.Tests/CoreTests.cs +++ b/src/Simulation/Simulators.Tests/CoreTests.cs @@ -30,14 +30,14 @@ public CoreTests(ITestOutputHelper output) public void BasicExecution() { var asmPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - var exe = Path.Combine(asmPath, "TestExe", "QsharpExe.exe"); + var exe = Path.Combine(asmPath, "TestExe", "QsharpExe.dll"); - ProcessRunner.Run(exe, "", out var _, out StringBuilder error, out int exitCode, out Exception ex); + ProcessRunner.Run("dotnet", exe, out var _, out StringBuilder error, out int exitCode, out Exception ex); Assert.Null(ex); Assert.Equal(1, exitCode); Assert.Contains("NotImplementedException", error.ToString()); - ProcessRunner.Run(exe, "--simulator QuantumSimulator", out var _, out error, out exitCode, out ex); + ProcessRunner.Run("dotnet", $"{exe} --simulator QuantumSimulator", out var _, out error, out exitCode, out ex); Assert.Null(ex); Assert.Equal(0, exitCode); Assert.Empty(error.ToString().Trim()); From f1dc2f68ee674ef00203741e547fa2c1357ebfc6 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 22 Apr 2020 16:47:56 -0700 Subject: [PATCH 69/70] Add TODO --- src/Simulation/CsharpGeneration/EntryPoint.fs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 94bea39834b..0b1969b169e 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -67,6 +67,7 @@ let rec private parameters context doc = function /// 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" From 0de879de0982618d3f24965877e8a90f5cb0156d Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 22 Apr 2020 18:33:52 -0700 Subject: [PATCH 70/70] Remove space-separated range parsing --- .../CsharpGeneration.Tests/EntryPointTests.fs | 20 +++++---- .../Resources/EntryPointDriver.cs | 41 ++++--------------- 2 files changed, 20 insertions(+), 41 deletions(-) diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index f2344ec3f4f..038acd5d4f0 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -243,20 +243,22 @@ let ``Accepts Range`` () = given ["-r"; "0..0"] |> yields "0..1..0" given ["-r"; "0..1"] |> yields "0..1..1" given ["-r"; "0..2..10"] |> yields "0..2..10" - given ["-r"; "0"; "..1"] |> yields "0..1..1" - given ["-r"; "0.."; "1"] |> yields "0..1..1" - given ["-r"; "0"; ".."; "1"] |> yields "0..1..1" - given ["-r"; "0"; "..2"; "..10"] |> yields "0..2..10" - given ["-r"; "0.."; "2"; "..10"] |> yields "0..2..10" - given ["-r"; "0"; ".."; "2"; ".."; "10"] |> yields "0..2..10" - given ["-r"; "0"; "1"] |> yields "0..1..1" - given ["-r"; "0"; "2"; "10"] |> yields "0..2..10" + given ["-r"; "0 ..1"] |> yields "0..1..1" + given ["-r"; "0.. 1"] |> yields "0..1..1" + given ["-r"; "0 .. 1"] |> yields "0..1..1" + given ["-r"; "0 ..2 ..10"] |> yields "0..2..10" + given ["-r"; "0.. 2 ..10"] |> yields "0..2..10" + given ["-r"; "0 .. 2 .. 10"] |> yields "0..2..10" + given ["-r"; "0 1"] |> fails + given ["-r"; "0 2 10"] |> fails + given ["-r"; "0"; "1"] |> fails given ["-r"; "0"] |> fails given ["-r"; "0.."] |> fails given ["-r"; "0..2.."] |> fails given ["-r"; "0..2..3.."] |> fails given ["-r"; "0..2..3..4"] |> fails - given ["-r"; "0"; "1"; "2"; "3"] |> fails + given ["-r"; "0"; ".."; "1"] |> fails + given ["-r"; "0..1"; "..2"] |> fails [] let ``Accepts String`` () = diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs index 276416690a9..672ee3b049f 100644 --- a/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs +++ b/src/Simulation/CsharpGeneration/Resources/EntryPointDriver.cs @@ -65,16 +65,16 @@ internal static Argument ResultArgumentHandler internal static Argument RangeArgumentHandler => new Argument(result => { var option = ((OptionResult)result.Parent).Token.Value; - var value = string.Join(' ', result.Tokens.Select(token => token.Value)); - return new[] + var value = result.Tokens.Single().Value; + var range = ParseRangeFromEnumerable(option, value, value.Split("..")); + if (range.IsFailure) { - ParseRangeFromEnumerable(option, value, result.Tokens.Select(token => token.Value)), - ParseRangeFromEnumerable(option, value, value.Split("..")) + result.ErrorMessage = range.ErrorMessage; } - .Choose(errors => result.ErrorMessage = string.Join('\n', errors.Distinct())); + return range.ValueOrDefault; }) { - Arity = ArgumentArity.OneOrMore + Arity = ArgumentArity.ExactlyOne }; /// @@ -326,14 +326,15 @@ protected override string ArgumentDescriptor(IArgument argument) internal struct Result { public bool IsSuccess { get; } - public T Value { 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; - Value = value; + ValueOrDefault = value; ErrorMessage = errorMessage; } @@ -389,29 +390,5 @@ internal static void Then(this Result result, Action onSuccess) onSuccess(result.Value); } } - - /// - /// Chooses the first successful result out of an enumerable of results. - /// - /// The type of the result values. - /// The results to choose from. - /// - /// The action to call with an enumerable of error messages if all of the results are failures. - /// - /// - /// The value of the first successful result, or default if none of the results were successful. - /// - internal static T Choose(this IEnumerable> results, Action> onError) - { - if (results.Any(result => result.IsSuccess)) - { - return results.First(attempt => attempt.IsSuccess).Value; - } - else - { - onError(results.Where(result => result.IsFailure).Select(result => result.ErrorMessage)); - return default; - } - } } }