diff --git a/.gitignore b/.gitignore index e1cb5a9ea3f..96ada8e2b36 100644 --- a/.gitignore +++ b/.gitignore @@ -336,10 +336,16 @@ ASALocalRun/ # MFractors (Xamarin productivity tool) working folder .mfractor/ + /src/Simulation/Simulators.Tests/TestProjects/QSharpExe/built +/src/Simulation/Simulators.Tests/TestProjects/QirExe/qir /src/Simulation/Simulators.Tests/TestProjects/TargetedExe/built + dbw_test +# out folders +out/ + # Jupyter caches .ipynb_checkpoints diff --git a/Simulation.sln b/Simulation.sln index a540b7cb492..5d4f6e60d76 100644 --- a/Simulation.sln +++ b/Simulation.sln @@ -103,11 +103,21 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StandaloneInputReference", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "qir-standalone-input-reference", "src\Qir\Samples\StandaloneInputReference\qsharp\qir-standalone-input-reference.csproj", "{D7D34736-A719-4B45-A33F-2723F59EC29D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Quantum.AutoSubstitution", "src\Simulation\AutoSubstitution\Microsoft.Quantum.AutoSubstitution.csproj", "{33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Execution", "Execution", "{442E66C8-F69F-44E9-9CD9-1F52C37EA41B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Quantum.Qir.Tools", "src\Qir\Execution\Tools\Microsoft.Quantum.Qir.Tools.csproj", "{C60226E3-98DE-4E92-AED4-B4A93D4CA063}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Microsoft.Quantum.Qir.Tools", "src\Qir\Execution\Tools.Tests\Tests.Microsoft.Quantum.Qir.Tools.csproj", "{4794FC80-4594-403F-AFEC-4889EFE87EA0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QirExe", "src\Simulation\Simulators.Tests\TestProjects\QirExe\QirExe.csproj", "{A0B98D0E-FC28-4FD8-8806-4825B9F5489D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Quantum.Qir.CommandLineTool", "src\Qir\Execution\CommandLineTool\Microsoft.Quantum.Qir.CommandLineTool.csproj", "{EAABB911-02EE-4A05-B566-0084013FC5CD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Quantum.AutoSubstitution", "src\Simulation\AutoSubstitution\Microsoft.Quantum.AutoSubstitution.csproj", "{33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Microsoft.Quantum.AutoSubstitution", "src\Simulation\AutoSubstitution.Tests\Tests.Microsoft.Quantum.AutoSubstitution.csproj", "{4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Microsoft.Quantum.AutoSubstitution.Integration", "src\Simulation\AutoSubstitution.Integration.Tests\Tests.Microsoft.Quantum.AutoSubstitution.Integration.csproj", "{D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Microsoft.Quantum.AutoSubstitution.Integration", "src\Simulation\AutoSubstitution.Integration.Tests\Tests.Microsoft.Quantum.AutoSubstitution.Integration.csproj", "{D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -681,6 +691,70 @@ Global {D7D34736-A719-4B45-A33F-2723F59EC29D}.RelWithDebInfo|Any CPU.Build.0 = Debug|Any CPU {D7D34736-A719-4B45-A33F-2723F59EC29D}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU {D7D34736-A719-4B45-A33F-2723F59EC29D}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU + {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.Debug|x64.ActiveCfg = Debug|Any CPU + {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.Debug|x64.Build.0 = Debug|Any CPU + {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.Release|Any CPU.Build.0 = Release|Any CPU + {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.Release|x64.ActiveCfg = Release|Any CPU + {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.Release|x64.Build.0 = Release|Any CPU + {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.Debug|x64.ActiveCfg = Debug|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.Debug|x64.Build.0 = Debug|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.Release|Any CPU.Build.0 = Release|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.Release|x64.ActiveCfg = Release|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.Release|x64.Build.0 = Release|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {A0B98D0E-FC28-4FD8-8806-4825B9F5489D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0B98D0E-FC28-4FD8-8806-4825B9F5489D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0B98D0E-FC28-4FD8-8806-4825B9F5489D}.Debug|x64.ActiveCfg = Debug|Any CPU + {A0B98D0E-FC28-4FD8-8806-4825B9F5489D}.Debug|x64.Build.0 = Debug|Any CPU + {A0B98D0E-FC28-4FD8-8806-4825B9F5489D}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {A0B98D0E-FC28-4FD8-8806-4825B9F5489D}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {A0B98D0E-FC28-4FD8-8806-4825B9F5489D}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {A0B98D0E-FC28-4FD8-8806-4825B9F5489D}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {A0B98D0E-FC28-4FD8-8806-4825B9F5489D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0B98D0E-FC28-4FD8-8806-4825B9F5489D}.Release|Any CPU.Build.0 = Release|Any CPU + {A0B98D0E-FC28-4FD8-8806-4825B9F5489D}.Release|x64.ActiveCfg = Release|Any CPU + {A0B98D0E-FC28-4FD8-8806-4825B9F5489D}.Release|x64.Build.0 = Release|Any CPU + {A0B98D0E-FC28-4FD8-8806-4825B9F5489D}.RelWithDebInfo|Any CPU.ActiveCfg = Debug|Any CPU + {A0B98D0E-FC28-4FD8-8806-4825B9F5489D}.RelWithDebInfo|Any CPU.Build.0 = Debug|Any CPU + {A0B98D0E-FC28-4FD8-8806-4825B9F5489D}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU + {A0B98D0E-FC28-4FD8-8806-4825B9F5489D}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU + {EAABB911-02EE-4A05-B566-0084013FC5CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EAABB911-02EE-4A05-B566-0084013FC5CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EAABB911-02EE-4A05-B566-0084013FC5CD}.Debug|x64.ActiveCfg = Debug|Any CPU + {EAABB911-02EE-4A05-B566-0084013FC5CD}.Debug|x64.Build.0 = Debug|Any CPU + {EAABB911-02EE-4A05-B566-0084013FC5CD}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {EAABB911-02EE-4A05-B566-0084013FC5CD}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {EAABB911-02EE-4A05-B566-0084013FC5CD}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {EAABB911-02EE-4A05-B566-0084013FC5CD}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {EAABB911-02EE-4A05-B566-0084013FC5CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EAABB911-02EE-4A05-B566-0084013FC5CD}.Release|Any CPU.Build.0 = Release|Any CPU + {EAABB911-02EE-4A05-B566-0084013FC5CD}.Release|x64.ActiveCfg = Release|Any CPU + {EAABB911-02EE-4A05-B566-0084013FC5CD}.Release|x64.Build.0 = Release|Any CPU + {EAABB911-02EE-4A05-B566-0084013FC5CD}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {EAABB911-02EE-4A05-B566-0084013FC5CD}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {EAABB911-02EE-4A05-B566-0084013FC5CD}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {EAABB911-02EE-4A05-B566-0084013FC5CD}.RelWithDebInfo|x64.Build.0 = Release|Any CPU {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.Debug|Any CPU.Build.0 = Debug|Any CPU {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -777,6 +851,11 @@ Global {AAFB81D3-BC87-404D-BA64-AF40B2D2E45A} = {F6C2D4C0-12DC-40E3-9C86-FA5308D9B567} {A7DB7367-9FD6-4164-8263-A05077BE54AB} = {AAFB81D3-BC87-404D-BA64-AF40B2D2E45A} {D7D34736-A719-4B45-A33F-2723F59EC29D} = {A7DB7367-9FD6-4164-8263-A05077BE54AB} + {442E66C8-F69F-44E9-9CD9-1F52C37EA41B} = {F6C2D4C0-12DC-40E3-9C86-FA5308D9B567} + {C60226E3-98DE-4E92-AED4-B4A93D4CA063} = {442E66C8-F69F-44E9-9CD9-1F52C37EA41B} + {4794FC80-4594-403F-AFEC-4889EFE87EA0} = {442E66C8-F69F-44E9-9CD9-1F52C37EA41B} + {A0B98D0E-FC28-4FD8-8806-4825B9F5489D} = {09C842CB-930C-4C7D-AD5F-E30DE4A55820} + {EAABB911-02EE-4A05-B566-0084013FC5CD} = {442E66C8-F69F-44E9-9CD9-1F52C37EA41B} {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F} = {34D419E9-CCF1-4E48-9FA4-3AD4B86BEEB4} {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300} = {34D419E9-CCF1-4E48-9FA4-3AD4B86BEEB4} {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C} = {34D419E9-CCF1-4E48-9FA4-3AD4B86BEEB4} diff --git a/build/manifest.ps1 b/build/manifest.ps1 index 5de30d17b65..6b45da5bc5f 100644 --- a/build/manifest.ps1 +++ b/build/manifest.ps1 @@ -26,6 +26,7 @@ $artifacts = @{ "Microsoft.Quantum.Type1.Core", "Microsoft.Quantum.Type2.Core", "Microsoft.Quantum.Type3.Core", + "Microsoft.Quantum.Qir.Tools", "Microsoft.Quantum.QSharp.Foundation" "Microsoft.Quantum.Runtime.Core", "Microsoft.Quantum.Simulators", @@ -35,6 +36,7 @@ $artifacts = @{ Assemblies = @( ".\src\Azure\Azure.Quantum.Client\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Azure.Quantum.Client.dll", ".\src\Simulation\AutoSubstitution\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.AutoSubstitution.dll", + ".\src\Qir\Execution\Tools\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.Qir.Tools.dll", ".\src\Simulation\Core\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.Runtime.Core.dll", ".\src\Simulation\EntryPointDriver\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.EntryPointDriver.dll", ".\src\Simulation\QSharpCore\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.QSharp.Core.dll", diff --git a/build/pack.ps1 b/build/pack.ps1 index fab179be420..9b17a17a264 100644 --- a/build/pack.ps1 +++ b/build/pack.ps1 @@ -163,12 +163,16 @@ Pack-Dotnet '../src/Simulation/QSharpCore/Microsoft.Quantum.QSharp.Core.csproj' Pack-Dotnet '../src/Simulation/Type1Core/Microsoft.Quantum.Type1.Core.csproj' Pack-Dotnet '../src/Simulation/Type2Core/Microsoft.Quantum.Type2.Core.csproj' Pack-Dotnet '../src/Simulation/Type3Core/Microsoft.Quantum.Type3.Core.csproj' +Pack-Dotnet '../src/Qir/Execution/Tools/Microsoft.Quantum.Qir.Tools.csproj' -ForcePrerelease +Pack-Dotnet '../src/Qir/Execution/CommandLineTool/Microsoft.Quantum.Qir.CommandLineTool.csproj' -ForcePrerelease Pack-One '../src/Simulation/Simulators/Microsoft.Quantum.Simulators.nuspec' Pack-One '../src/Xunit/Microsoft.Quantum.Xunit.csproj' Pack-Crate -PackageDirectory "../src/Simulation/qdk_sim_rs" -OutPath $Env:CRATE_OUTDIR; Pack-Wheel -PackageDirectory "../src/Simulation/qdk_sim_rs" -OutPath $Env:WHEEL_OUTDIR; Pack-One '../src/Qir/Runtime/Microsoft.Quantum.Qir.Runtime.nuspec' -ForcePrerelease +Move-Item -Path (Join-Path $Env:NUGET_OUTDIR Microsoft.Quantum.Qir.CommandLineTool.*) -Destination $Env:INTERNAL_TOOLS_OUTDIR + if (-not $all_ok) { throw "At least one project failed to pack. Check the logs." } diff --git a/build/set-env.ps1 b/build/set-env.ps1 index 1173a7a0a07..69808d051e6 100644 --- a/build/set-env.ps1 +++ b/build/set-env.ps1 @@ -25,6 +25,8 @@ if ($Env:DROP_NATIVE -eq $null) { $Env:DROP_NATIVE = (Join-Path $PSScriptRoot "..") } +If ($Env:INTERNAL_TOOLS_OUTDIR -eq $null) { $Env:INTERNAL_TOOLS_OUTDIR = (Join-Path $Env:DROPS_DIR "internal_tools") } +If (-not (Test-Path -Path $Env:INTERNAL_TOOLS_OUTDIR)) { [IO.Directory]::CreateDirectory($Env:INTERNAL_TOOLS_OUTDIR) } If ($Env:NUGET_OUTDIR -eq $null) { $Env:NUGET_OUTDIR = (Join-Path $Env:DROPS_DIR "nugets") } If (-not (Test-Path -Path $Env:NUGET_OUTDIR)) { [IO.Directory]::CreateDirectory($Env:NUGET_OUTDIR) } diff --git a/build/test.ps1 b/build/test.ps1 index 76d614af61c..211c5931bd6 100644 --- a/build/test.ps1 +++ b/build/test.ps1 @@ -43,11 +43,13 @@ if ($Env:ENABLE_QIRRUNTIME -ne "false") { if ($LastExitCode -ne 0) { $script:all_ok = $False } + $qirTests = (Join-Path $PSScriptRoot "../src/Qir/Tests") & "$qirTests/test-qir-tests.ps1" if ($LastExitCode -ne 0) { $script:all_ok = $False } + $qirSamples = (Join-Path $PSScriptRoot "../src/Qir/Samples") & "$qirSamples/test-qir-samples.ps1" if ($LastExitCode -ne 0) { diff --git a/global.json b/global.json index 62bca5f3c81..b1cbde009e2 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "msbuild-sdks": { - "Microsoft.Quantum.Sdk": "0.18.2106149090-alpha" + "Microsoft.Quantum.Sdk": "0.18.2107150698-alpha" } } diff --git a/src/Azure/Azure.Quantum.Client/Machine/QuantumMachineFactory.cs b/src/Azure/Azure.Quantum.Client/Machine/QuantumMachineFactory.cs index cd61dd5ce49..98dfab392f9 100644 --- a/src/Azure/Azure.Quantum.Client/Machine/QuantumMachineFactory.cs +++ b/src/Azure/Azure.Quantum.Client/Machine/QuantumMachineFactory.cs @@ -17,6 +17,7 @@ public static class QuantumMachineFactory /// The execution target for job submission. /// The connection string for the Azure storage account. /// A quantum machine for job submission targeting targetName. + [Obsolete("Use SubmitterFactory.QSharpSubmitter.")] public static IQuantumMachine? CreateMachine( IWorkspace workspace, string targetName, string? storageConnectionString = null) { diff --git a/src/Azure/Azure.Quantum.Client/Microsoft.Azure.Quantum.Client.csproj b/src/Azure/Azure.Quantum.Client/Microsoft.Azure.Quantum.Client.csproj index 28a0ded1164..5585bc6f1b4 100644 --- a/src/Azure/Azure.Quantum.Client/Microsoft.Azure.Quantum.Client.csproj +++ b/src/Azure/Azure.Quantum.Client/Microsoft.Azure.Quantum.Client.csproj @@ -6,6 +6,7 @@ netstandard2.1 x64 Client library for Azure Quantum. + Microsoft.Azure.Quantum diff --git a/src/Azure/Azure.Quantum.Client/SubmitterFactory.cs b/src/Azure/Azure.Quantum.Client/SubmitterFactory.cs new file mode 100644 index 00000000000..d4e303cf707 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/SubmitterFactory.cs @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +namespace Microsoft.Azure.Quantum +{ + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Linq; + using System.Reflection; + using System.Text.RegularExpressions; + using Microsoft.Quantum.Runtime.Submitters; + + /// + /// Creates Azure Quantum submitters for specific targets. + /// + public static class SubmitterFactory + { + /// + /// Information about each supported QIR submitter. + /// + private static readonly ImmutableList QirSubmitters = ImmutableList.Create( + new SubmitterInfo( + new Regex(@"\Amicrosoft\.simulator\.([\w]+\.)*[\w]+\z"), + "Microsoft.Quantum.Providers.Targets.MicrosoftSimulatorSubmitter, Microsoft.Quantum.Providers.Core", + "QirSubmitter")); + + /// + /// Information about each supported Q# submitter. + /// + private static readonly ImmutableList QSharpSubmitters = ImmutableList.Empty; + + /// + /// Returns a QIR submitter. + /// + /// The name of the execution target. + /// The workspace used to manage jobs. + /// The connection string for the storage account. + /// A QIR submitter. + public static IQirSubmitter? QirSubmitter(string target, IWorkspace workspace, string? storageConnection) => + Submitter(QirSubmitters, target, workspace, storageConnection); + + /// + /// Returns a Q# submitter. + /// + /// The name of the execution target. + /// The workspace used to manage jobs. + /// The connection string for the storage account. + /// A Q# submitter. + public static IQSharpSubmitter? QSharpSubmitter( + string target, IWorkspace workspace, string? storageConnection) => + Submitter(QSharpSubmitters, target, workspace, storageConnection); + + /// + /// Returns an instance of a submitter from the given list that matches the target. + /// + /// Information about each submitter. + /// The name of the execution target. + /// The workspace used to manage jobs. + /// The connection string for the storage account. + /// The type of the submitter interface. + /// The submitter instance. + private static T? Submitter( + IEnumerable submitters, string target, IWorkspace workspace, string? storageConnection) + where T : class + { + var submitter = submitters.FirstOrDefault(s => s.TargetPattern.IsMatch(target)); + if (submitter is null) + { + return null; + } + + var type = QdkType(submitter.TypeName); + var args = new object?[] { target, workspace, storageConnection }; + return (T)type.InvokeMember( + submitter.MethodName, BindingFlags.InvokeMethod, Type.DefaultBinder, null, args); + } + + /// + /// Returns a type from a QDK assembly given its type name. + /// + /// The fully-qualified or assembly-qualified type name. + /// The type. + private static Type QdkType(string name) + { + var version = Assembly.GetExecutingAssembly().GetName().Version; + var strongName = $"{name}, Version={version}, Culture=neutral, PublicKeyToken=40866b40fd95c7f5"; + return Type.GetType(strongName, true); + } + + /// + /// Information about a submitter. + /// + private class SubmitterInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The pattern for targets supported by the submitter. + /// The fully-qualified or assembly-qualified name of the submitter type. + /// The name of the static factory method. + public SubmitterInfo(Regex targetPattern, string typeName, string methodName) => + (TargetPattern, TypeName, MethodName) = (targetPattern, typeName, methodName); + + /// + /// The pattern for targets supported by the submitter. + /// + public Regex TargetPattern { get; } + + /// + /// The fully-qualified or assembly-qualified name of the submitter type. + /// + public string TypeName { get; } + + /// + /// The name of the static factory method. + /// + public string MethodName { get; } + } + } +} diff --git a/src/Qir/Execution/CommandLineTool/Microsoft.Quantum.Qir.CommandLineTool.csproj b/src/Qir/Execution/CommandLineTool/Microsoft.Quantum.Qir.CommandLineTool.csproj new file mode 100644 index 00000000000..95fd980a0ec --- /dev/null +++ b/src/Qir/Execution/CommandLineTool/Microsoft.Quantum.Qir.CommandLineTool.csproj @@ -0,0 +1,20 @@ + + + + Exe + netcoreapp3.1 + + + + true + qir-cli + Command-line tool for interacting with QIR. + Quantum QIR + + + + + + + + diff --git a/src/Qir/Execution/CommandLineTool/Program.cs b/src/Qir/Execution/CommandLineTool/Program.cs new file mode 100644 index 00000000000..9ffd39908bb --- /dev/null +++ b/src/Qir/Execution/CommandLineTool/Program.cs @@ -0,0 +1,150 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.CommandLine; +using System.CommandLine.Builder; +using System.CommandLine.Help; +using System.CommandLine.Invocation; +using System.CommandLine.Parsing; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Quantum.Qir.Tools; + +namespace Microsoft.Quantum.Qir.CommandLineTool +{ + class Program + { + private static async Task Main(string[] args) + { + var buildCommand = CreateBuildCommand(); + + var root = new RootCommand() { buildCommand }; + SetSubCommandAsDefault(root, buildCommand); + root.Description = "Command-line tool for processing QIR DLL files."; + root.TreatUnmatchedTokensAsErrors = true; + + Console.OutputEncoding = Encoding.UTF8; + return await new CommandLineBuilder(root) + .UseDefaults() + .UseHelpBuilder(context => new QsHelpBuilder(context.Console)) + .Build() + .InvokeAsync(args); + } + + /// + /// Creates the Build command for the command line compiler, which is used to + /// compile the executables from a given QIR DLL. + /// + /// The Build command. + private static Command CreateBuildCommand() + { + var buildCommand = new Command("build", "(default) Build the executables from a QIR DLL.") + { + Handler = CommandHandler.Create((BuildOptions settings) => + QirTools.BuildFromQSharpDll(settings.QSharpDll, settings.LibraryDirectories, settings.IncludeDirectories, settings.ExecutablesDirectory)) + }; + buildCommand.TreatUnmatchedTokensAsErrors = true; + + buildCommand.AddOption(new Option( + aliases: new string[] { "--qsharp-dll", "--dll" }, + description: "The path to the .NET DLL file generated by the Q# compiler.") + { + Required = true + }); + + buildCommand.AddOption(new Option( + aliases: new string[] { "--library-directories", "--lib" }, + description: "One or more paths to the directories containing the libraries to be linked.") + { + Required = false, + Argument = new Argument(() => new DirectoryInfo[] { }) + { + Arity = ArgumentArity.OneOrMore + } + }); + + buildCommand.AddOption(new Option( + aliases: new string[] { "--include-directories", "--include" }, + description: "One or more paths to the directories containing the headers required for compilation.") + { + Required = false, + Argument = new Argument(() => new DirectoryInfo[] { }) + { + Arity = ArgumentArity.OneOrMore + } + }); + + buildCommand.AddOption(new Option( + aliases: new string[] { "--executables-directory", "--exe" }, + description: "The path to the output directory where the created executables will be placed.") + { + Required = true + }); + + return buildCommand; + } + + /// + /// Copies the handle and options from the given sub command to the given command. + /// + /// The command whose handle and options will be set. + /// The sub command that will be copied from. + private static void SetSubCommandAsDefault(Command root, Command subCommand) + { + root.Handler = subCommand.Handler; + foreach (var option in subCommand.Options) + { + root.AddOption(option); + } + } + + /// + /// A modification of the command-line class. + /// + private sealed class QsHelpBuilder : HelpBuilder + { + /// + /// Creates a new help builder using the given console. + /// + /// The console to use. + internal 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; + } + } + + /// + /// A class for encapsulating the different options for the build command. + /// + public sealed class BuildOptions + { + /// + /// The path to the .NET DLL file generated by the Q# compiler. + /// + public FileInfo QSharpDll { get; set; } + + /// + /// One or more paths to the directories containing the libraries to be linked. + /// + public DirectoryInfo[] LibraryDirectories { get; set; } + + /// + /// One or more paths to the directories containing the headers required for compilation. + /// + public DirectoryInfo[] IncludeDirectories { get; set; } + + /// + /// The path to the output directory where the created executables will be placed. + /// + public DirectoryInfo ExecutablesDirectory { get; set; } + } + } +} diff --git a/src/Qir/Execution/Tools.Tests/QirDriverGeneratorTests.cs b/src/Qir/Execution/Tools.Tests/QirDriverGeneratorTests.cs new file mode 100644 index 00000000000..ece6d0e22fd --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/QirDriverGeneratorTests.cs @@ -0,0 +1,180 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using System.Text; + +using Xunit; + +using Microsoft.Quantum.Qir.Tools.Driver; +using Microsoft.Quantum.Qir.Serialization; + +namespace Tests.Microsoft.Quantum.Qir.Tools +{ + public class QirDriverGeneratorTests + { + private static string TestArtifactsDirectory = Path.Combine("TestArtifacts", "FullStateDriverGenerator"); + private static string TestCasesDirectory = Path.Combine("TestCases", "FullStateDriverGenerator"); + private static IDictionary TestCases = + new Dictionary + { + { + "UseNoArgs", + new EntryPointOperation{Name = "UseNoArgs"} + }, + { + "UseBoolArg", + new EntryPointOperation + { + Name = "UseBoolArg", + Parameters = new List{new Parameter{ Name = "BoolArg", Type = DataType.BoolType}} + } + }, + { + "UseBoolArrayArg", + new EntryPointOperation + { + Name = "UseBoolArrayArg", + Parameters = new List{new Parameter{ Name = "BoolArrayArg", Type = DataType.ArrayType, ArrayType = DataType.BoolType}} + } + }, + { + "UseDoubleArg", + new EntryPointOperation + { + Name = "UseDoublArg", + Parameters = new List{new Parameter{ Name = "DoubleArg", Type = DataType.DoubleType}} + } + }, + { + "UseDoubleArrayArg", + new EntryPointOperation + { + Name = "UseDoubleArrayArg", + Parameters = new List{new Parameter{ Name = "DoubleArrayArg", Type = DataType.ArrayType, ArrayType = DataType.DoubleType}} + } + }, + { + "UseIntegerArg", + new EntryPointOperation + { + Name = "UseIntegerArg", + Parameters = new List{new Parameter{ Name = "IntegerArg", Type = DataType.IntegerType}} + } + }, + { + "UseIntegerArrayArg", + new EntryPointOperation + { + Name = "UseIntegerArrayArg", + Parameters = new List{new Parameter{ Name = "IntegerArrayArg", Type = DataType.ArrayType, ArrayType = DataType.IntegerType}} + } + }, + { + "UsePauliArg", + new EntryPointOperation + { + Name = "UsePauliArg", + Parameters = new List{new Parameter{ Name = "PauliArg", Type = DataType.PauliType}} + } + }, + { + "UsePauliArrayArg", + new EntryPointOperation + { + Name = "UsePauliArrayArg", + Parameters = new List{new Parameter{ Name = "PauliArrayArg", Type = DataType.ArrayType, ArrayType = DataType.PauliType}} + } + }, + { + "UseRangeArg", + new EntryPointOperation + { + Name = "UseRangeArg", + Parameters = new List{new Parameter{ Name = "RangeArg", Type = DataType.RangeType}} + } + }, + { + "UseRangeArrayArg", + new EntryPointOperation + { + Name = "UseRangeArrayArg", + Parameters = new List{new Parameter{ Name = "RangeArrayArg", Type = DataType.ArrayType, ArrayType = DataType.RangeType}} + } + }, + { + "UseResultArg", + new EntryPointOperation + { + Name = "UseResultArg", + Parameters = new List{new Parameter{ Name = "ResultArg", Type = DataType.ResultType}} + } + }, + { + "UseResultArrayArg", + new EntryPointOperation + { + Name = "UseResultArrayArg", + Parameters = new List{new Parameter{ Name = "ResultArrayArg", Type = DataType.ArrayType, ArrayType = DataType.ResultType}} + } + }, + { + "UseStringArg", + new EntryPointOperation + { + Name = "UseStringArg", + Parameters = new List{new Parameter{ Name = "StringArg", Type = DataType.StringType}} + } + }, + { + "UseMiscArgs", + new EntryPointOperation + { + Name = "UseMiscArgs", + Parameters = new List{ + new Parameter{ Name = "BoolArg", Type = DataType.BoolType}, + new Parameter{ Name = "IntegerArrayArg", Position = 1, Type = DataType.ArrayType, ArrayType = DataType.IntegerType}, + new Parameter{ Name = "RangeArg", Position = 2, Type = DataType.RangeType}, + new Parameter{ Name = "StringArg", Position = 3, Type = DataType.StringType} + } + } + } + }; + + private static string RemoveLineEndings(string str) => + str.Replace("\n", string.Empty).Replace("\r", string.Empty); + + [Theory] + [InlineData("UseNoArgs")] + [InlineData("UseBoolArg")] + [InlineData("UseBoolArrayArg")] + [InlineData("UseDoubleArg")] + [InlineData("UseDoubleArrayArg")] + [InlineData("UseIntegerArg")] + [InlineData("UseIntegerArrayArg")] + [InlineData("UsePauliArg")] + [InlineData("UsePauliArrayArg")] + [InlineData("UseRangeArg")] + [InlineData("UseRangeArrayArg")] + [InlineData("UseResultArg")] + [InlineData("UseResultArrayArg")] + [InlineData("UseStringArg")] + [InlineData("UseMiscArgs")] + public void GenerateFullStateSimulatorDriver(string testCase) + { + var entryPointOperation = TestCases[testCase]; + var driverGenerator = new QirFullStateDriverGenerator(); + var driverFileName = $"{testCase}.cpp"; + var verificationCppSourceCode = RemoveLineEndings(File.ReadAllText(Path.Combine(TestCasesDirectory, driverFileName))); + Directory.CreateDirectory(TestArtifactsDirectory); + var generatedStream = File.Create(Path.Combine(TestArtifactsDirectory, driverFileName)); + driverGenerator.GenerateAsync(entryPointOperation, generatedStream).Wait(); + var generatedStreamReader = new StreamReader(generatedStream, Encoding.UTF8); + var generatedCppSourceCode = RemoveLineEndings(generatedStreamReader.ReadToEnd()); + Assert.Equal(verificationCppSourceCode, generatedCppSourceCode); + generatedStream.Close(); + + } + } +} diff --git a/src/Qir/Execution/Tools.Tests/QirExecutableGeneratorTests.cs b/src/Qir/Execution/Tools.Tests/QirExecutableGeneratorTests.cs new file mode 100644 index 00000000000..61af04407e8 --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/QirExecutableGeneratorTests.cs @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Quantum.Qir.Tools.Executable; +using Moq; +using Xunit; + +namespace Tests.Microsoft.Quantum.Qir.Tools +{ + public class QirExecutableGeneratorTests : IDisposable + { + private readonly Mock clangClientMock; + private readonly QirExecutableGenerator executableGenerator; + private readonly DirectoryInfo sourceDirectory; + private readonly DirectoryInfo includeDirectory; + private readonly DirectoryInfo libraryDirectory; + private readonly DirectoryInfo binDirectory; + private readonly IList libraryFiles; + private readonly IList sourceFiles; + private IList linkLibraries; + + public QirExecutableGeneratorTests() + { + clangClientMock = new Mock(); + executableGenerator = new QirExecutableGenerator(clangClientMock.Object, null); + + // Set up files. + var prefix = Guid.NewGuid().ToString(); + binDirectory = new DirectoryInfo($"{prefix}-bin"); + binDirectory.Create(); + libraryDirectory = new DirectoryInfo($"{prefix}-library"); + libraryDirectory.Create(); + libraryFiles = new List() + { + Util.CreateBinaryFile(libraryDirectory, "lib1", new byte[]{0x01, 0x23, 0x45, 0x67}), + Util.CreateBinaryFile(libraryDirectory, "lib2", new byte[]{0x89, 0xAB, 0xCD, 0xEF}), + }; + includeDirectory = new DirectoryInfo($"{prefix}-include"); + includeDirectory.Create(); + sourceDirectory = new DirectoryInfo($"{prefix}-source"); + sourceDirectory.Create(); + sourceFiles = new List() + { + Util.CreateTextFile(sourceDirectory, "src1.cpp", "src1 contents"), + Util.CreateBinaryFile(sourceDirectory, "src2.bc", new byte[]{0xFE, 0xDC, 0xBA, 0x98}), + }; + linkLibraries = new List { "lib1", "lib2" }; + } + + public void Dispose() + { + sourceDirectory.Delete(true); + includeDirectory.Delete(true); + libraryDirectory.Delete(true); + binDirectory.Delete(true); + } + + [Fact] + public async Task TestGenerateExecutable() + { + var executableFile = new FileInfo(Path.Combine(binDirectory.FullName, "executableFile")); + await executableGenerator.GenerateExecutableAsync(executableFile, sourceDirectory, new[] { libraryDirectory }, new[] { includeDirectory }, linkLibraries); + + // Verify invocation of clang. + clangClientMock.Verify(obj => obj.CreateExecutableAsync( + It.Is(s => s.OrderBy(val => val).SequenceEqual(sourceFiles.OrderBy(val => val.FullName).Select(fileInfo => fileInfo.FullName))), + It.Is(s => s.OrderBy(val => val).SequenceEqual(linkLibraries.OrderBy(val => val))), + new[] { libraryDirectory.FullName }, + new[] { includeDirectory.FullName }, + executableFile.FullName)); + + // Verify files were copied. + Assert.True(FilesWereCopied(libraryFiles.ToArray(), binDirectory)); + } + + private static bool FilesWereCopied(FileInfo[] files, DirectoryInfo destinationDirectory) + { + var destinationDirectoryFiles = destinationDirectory.GetFiles(); + + // N.B. Runtime is (number of FileInfo objects passed in) * (number of files in destination directory). + foreach (var file in files) + { + // Make sure that the file was copied and that the contents is the same. + var copiedFile = destinationDirectoryFiles.FirstOrDefault(fileInfo => fileInfo.Name == file.Name); + if (copiedFile == null) + { + return false; + } + + if (!Util.CompareFiles(file, copiedFile)) + { + return false; + } + + } + + return true; + } + } +} diff --git a/src/Qir/Execution/Tools.Tests/QirExecutableTests.cs b/src/Qir/Execution/Tools.Tests/QirExecutableTests.cs new file mode 100644 index 00000000000..6ab9b829604 --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/QirExecutableTests.cs @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Quantum.Qir.Serialization; +using Microsoft.Quantum.Qir.Tools.Driver; +using Microsoft.Quantum.Qir.Tools.Executable; +using Moq; +using Xunit; + +namespace Tests.Microsoft.Quantum.Qir.Tools +{ + public class QirExecutableTests : IDisposable + { + private readonly DirectoryInfo sourceDirectory; + private readonly DirectoryInfo includeDirectory; + private readonly DirectoryInfo libraryDirectory; + private readonly DirectoryInfo binDirectory; + private readonly Mock driverGeneratorMock; + private readonly Mock executableGeneratorMock; + private readonly Mock runnerMock; + private readonly Mock qirExecutable; + private readonly FileInfo executableFile; + private readonly byte[] qirBytecode = { 1, 2, 3, 4, 5 }; + private readonly IList linkLibraries; + private readonly IList headerDirectories; + private readonly IList libraryDirectories; + + public QirExecutableTests() + { + // Set up files. + var prefix = Guid.NewGuid().ToString(); + binDirectory = new DirectoryInfo($"{prefix}-bin"); + binDirectory.Create(); + libraryDirectory = new DirectoryInfo($"{prefix}-library"); + libraryDirectory.Create(); + includeDirectory = new DirectoryInfo($"{prefix}-include"); + includeDirectory.Create(); + sourceDirectory = new DirectoryInfo($"{prefix}-source"); + sourceDirectory.Create(); + executableFile = new FileInfo(Path.Combine(binDirectory.FullName, "executable")); + driverGeneratorMock = new Mock(); + executableGeneratorMock = new Mock(); + runnerMock = new Mock(); + qirExecutable = new Mock(executableFile, qirBytecode, driverGeneratorMock.Object, executableGeneratorMock.Object, runnerMock.Object, null) { CallBase = true }; + linkLibraries = new List { "lib1", "lib2" }; + headerDirectories = new List(); + libraryDirectories = new List(); + qirExecutable.SetupGet(obj => obj.LinkLibraries).Returns(linkLibraries); + qirExecutable.SetupGet(obj => obj.SourceDirectoryPath).Returns(sourceDirectory.FullName); + qirExecutable.SetupGet(obj => obj.DriverFileExtension).Returns(".cpp"); + qirExecutable.SetupGet(obj => obj.HeaderDirectories).Returns(headerDirectories); + qirExecutable.SetupGet(obj => obj.LibraryDirectories).Returns(libraryDirectories); + } + + public void Dispose() + { + sourceDirectory.Delete(true); + includeDirectory.Delete(true); + libraryDirectory.Delete(true); + binDirectory.Delete(true); + } + + [Fact] + public async Task TestBuild() + { + // Set up. + var entryPoint = new EntryPointOperation(); + var driverFileContents = "driver file contents"; + driverGeneratorMock.Setup(obj => obj.GenerateAsync(entryPoint, It.IsAny())).Callback((entryPoint, stream) => + { + using var streamWriter = new StreamWriter(stream); + streamWriter.Write(driverFileContents); + }); + + // Build the executable. + await qirExecutable.Object.BuildAsync(entryPoint, new[] { libraryDirectory }, new[] { includeDirectory }); + + // Verify that the "bytecode" file was created correctly. + var bytecodeFilePath = new FileInfo(Path.Combine(sourceDirectory.FullName, "qir.bc")); + using var bytecodeFileStream = bytecodeFilePath.OpenRead(); + Assert.True(Util.CompareStreams(new MemoryStream(qirBytecode), bytecodeFileStream)); + + // Verify that the driver was written to the correct file. + var driver = new FileInfo(Path.Combine(sourceDirectory.FullName, "driver.cpp")); + using var driverStreamReader = driver.OpenText(); + var actualDriverContents = driverStreamReader.ReadToEnd(); + Assert.Equal(driverFileContents, actualDriverContents); + + // Verify that the executable was generated. + executableGeneratorMock.Verify(obj => obj.GenerateExecutableAsync(executableFile, It.Is(arg => arg.FullName == sourceDirectory.FullName), new[] { libraryDirectory }, new[] { includeDirectory }, linkLibraries)); + } + + [Fact] + public async Task TestRun() + { + // Set up. + using var outputStream = new MemoryStream(); + var execInfo = new ExecutionInformation(); + var arguments = "arguments"; + driverGeneratorMock.Setup(obj => obj.GetCommandLineArguments(execInfo)).Returns(arguments); + + // Run executable. + await qirExecutable.Object.RunAsync(execInfo, outputStream); + + // Verify runner was invoked properly. + runnerMock.Verify(obj => obj.RunExecutableAsync(executableFile, outputStream, arguments)); + } + } +} diff --git a/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseBoolArg.cpp b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseBoolArg.cpp new file mode 100644 index 00000000000..c7d4e5c5806 --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseBoolArg.cpp @@ -0,0 +1,89 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// This code was generated by the Microsoft.Quantum.Qir.Tools package. +// The purpose of this source code file is to provide an entry-point for executing a QIR program. +// It handles parsing of command line arguments, and it invokes an entry-point function exposed by the QIR program. +//---------------------------------------------------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "CLI11.hpp" + +#include "QirRuntime.hpp" +#include "QirContext.hpp" + +#include "SimFactory.hpp" + +using namespace Microsoft::Quantum; +using namespace std; + +// Auxiliary functions for interop with Q# Bool type. +const char InteropFalseAsChar = 0x0; +const char InteropTrueAsChar = 0x1; +map BoolAsCharMap{ + {"0", InteropFalseAsChar}, + {"false", InteropFalseAsChar}, + {"1", InteropTrueAsChar}, + {"true", InteropTrueAsChar} +}; + +extern "C" void UseBoolArg( + char BoolArg +); // QIR interop function. + +int main(int argc, char* argv[]) +{ + CLI::App app("QIR Standalone Entry Point"); + + // Initialize runtime. + unique_ptr sim = CreateFullstateSimulator(); + QirContextScope qirctx(sim.get(), false /*trackAllocatedObjects*/); + + // Add the --simulation-output option. + string simulationOutputFile; + CLI::Option* simulationOutputFileOpt = app.add_option( + "--simulation-output", + simulationOutputFile, + "File where the output produced during the simulation is written"); + + // Add a command line option for each entry-point parameter. + char BoolArgCli; + BoolArgCli = InteropFalseAsChar; + app.add_option("--BoolArg", BoolArgCli, "Option to provide a value for the BoolArg parameter") + ->required() + ->transform(CLI::CheckedTransformer(BoolAsCharMap, CLI::ignore_case)); + + // After all the options have been added, parse arguments from the command line. + CLI11_PARSE(app, argc, argv); + + // Cast parsed arguments to its interop types. + char BoolArgInterop = BoolArgCli; + + // Redirect the simulator output from std::cout if the --simulation-output option is present. + ostream* simulatorOutputStream = &cout; + ofstream simulationOutputFileStream; + if (!simulationOutputFileOpt->empty()) + { + simulationOutputFileStream.open(simulationOutputFile); + SetOutputStream(simulationOutputFileStream); + simulatorOutputStream = &simulationOutputFileStream; + } + + // Execute the entry point operation. + UseBoolArg( + BoolArgInterop + ); + + // Flush the output of the simulation. + simulatorOutputStream->flush(); + if (simulationOutputFileStream.is_open()) + { + simulationOutputFileStream.close(); + } + + return 0; +} diff --git a/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseBoolArrayArg.cpp b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseBoolArrayArg.cpp new file mode 100644 index 00000000000..20356a2a536 --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseBoolArrayArg.cpp @@ -0,0 +1,114 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// This code was generated by the Microsoft.Quantum.Qir.Tools package. +// The purpose of this source code file is to provide an entry-point for executing a QIR program. +// It handles parsing of command line arguments, and it invokes an entry-point function exposed by the QIR program. +//---------------------------------------------------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "CLI11.hpp" + +#include "QirRuntime.hpp" +#include "QirContext.hpp" + +#include "SimFactory.hpp" + +using namespace Microsoft::Quantum; +using namespace std; + +// Auxiliary functions for interop with Q# Array type. +struct InteropArray +{ + int64_t Size; + void* Data; + + InteropArray(int64_t size, void* data) : + Size(size), + Data(data){} +}; + +template +unique_ptr CreateInteropArray(vector& v) +{ + unique_ptr array(new InteropArray(v.size(), v.data())); + return array; +} + +template +void TranslateVector(vector& sourceVector, vector& destinationVector, function translationFunction) +{ + destinationVector.resize(sourceVector.size()); + transform(sourceVector.begin(), sourceVector.end(), destinationVector.begin(), translationFunction); +} + +// Auxiliary functions for interop with Q# Bool type. +const char InteropFalseAsChar = 0x0; +const char InteropTrueAsChar = 0x1; +map BoolAsCharMap{ + {"0", InteropFalseAsChar}, + {"false", InteropFalseAsChar}, + {"1", InteropTrueAsChar}, + {"true", InteropTrueAsChar} +}; + +extern "C" void UseBoolArrayArg( + InteropArray* BoolArrayArg +); // QIR interop function. + +int main(int argc, char* argv[]) +{ + CLI::App app("QIR Standalone Entry Point"); + + // Initialize runtime. + unique_ptr sim = CreateFullstateSimulator(); + QirContextScope qirctx(sim.get(), false /*trackAllocatedObjects*/); + + // Add the --simulation-output option. + string simulationOutputFile; + CLI::Option* simulationOutputFileOpt = app.add_option( + "--simulation-output", + simulationOutputFile, + "File where the output produced during the simulation is written"); + + // Add a command line option for each entry-point parameter. + vector BoolArrayArgCli; + app.add_option("--BoolArrayArg", BoolArrayArgCli, "Option to provide a value for the BoolArrayArg parameter") + ->required() + ->transform(CLI::CheckedTransformer(BoolAsCharMap, CLI::ignore_case)); + + // After all the options have been added, parse arguments from the command line. + CLI11_PARSE(app, argc, argv); + + // Cast parsed arguments to its interop types. + unique_ptr BoolArrayArgUniquePtr = CreateInteropArray(BoolArrayArgCli); + InteropArray* BoolArrayArgInterop = BoolArrayArgUniquePtr.get(); + + // Redirect the simulator output from std::cout if the --simulation-output option is present. + ostream* simulatorOutputStream = &cout; + ofstream simulationOutputFileStream; + if (!simulationOutputFileOpt->empty()) + { + simulationOutputFileStream.open(simulationOutputFile); + SetOutputStream(simulationOutputFileStream); + simulatorOutputStream = &simulationOutputFileStream; + } + + // Execute the entry point operation. + UseBoolArrayArg( + BoolArrayArgInterop + ); + + // Flush the output of the simulation. + simulatorOutputStream->flush(); + if (simulationOutputFileStream.is_open()) + { + simulationOutputFileStream.close(); + } + + return 0; +} diff --git a/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseDoubleArg.cpp b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseDoubleArg.cpp new file mode 100644 index 00000000000..fdee7680b61 --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseDoubleArg.cpp @@ -0,0 +1,78 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// This code was generated by the Microsoft.Quantum.Qir.Tools package. +// The purpose of this source code file is to provide an entry-point for executing a QIR program. +// It handles parsing of command line arguments, and it invokes an entry-point function exposed by the QIR program. +//---------------------------------------------------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "CLI11.hpp" + +#include "QirRuntime.hpp" +#include "QirContext.hpp" + +#include "SimFactory.hpp" + +using namespace Microsoft::Quantum; +using namespace std; + +extern "C" void UseDoublArg( + double_t DoubleArg +); // QIR interop function. + +int main(int argc, char* argv[]) +{ + CLI::App app("QIR Standalone Entry Point"); + + // Initialize runtime. + unique_ptr sim = CreateFullstateSimulator(); + QirContextScope qirctx(sim.get(), false /*trackAllocatedObjects*/); + + // Add the --simulation-output option. + string simulationOutputFile; + CLI::Option* simulationOutputFileOpt = app.add_option( + "--simulation-output", + simulationOutputFile, + "File where the output produced during the simulation is written"); + + // Add a command line option for each entry-point parameter. + double_t DoubleArgCli; + DoubleArgCli = 0.0; + app.add_option("--DoubleArg", DoubleArgCli, "Option to provide a value for the DoubleArg parameter") + ->required(); + + // After all the options have been added, parse arguments from the command line. + CLI11_PARSE(app, argc, argv); + + // Cast parsed arguments to its interop types. + double_t DoubleArgInterop = DoubleArgCli; + + // Redirect the simulator output from std::cout if the --simulation-output option is present. + ostream* simulatorOutputStream = &cout; + ofstream simulationOutputFileStream; + if (!simulationOutputFileOpt->empty()) + { + simulationOutputFileStream.open(simulationOutputFile); + SetOutputStream(simulationOutputFileStream); + simulatorOutputStream = &simulationOutputFileStream; + } + + // Execute the entry point operation. + UseDoublArg( + DoubleArgInterop + ); + + // Flush the output of the simulation. + simulatorOutputStream->flush(); + if (simulationOutputFileStream.is_open()) + { + simulationOutputFileStream.close(); + } + + return 0; +} diff --git a/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseDoubleArrayArg.cpp b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseDoubleArrayArg.cpp new file mode 100644 index 00000000000..a171287a3ad --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseDoubleArrayArg.cpp @@ -0,0 +1,103 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// This code was generated by the Microsoft.Quantum.Qir.Tools package. +// The purpose of this source code file is to provide an entry-point for executing a QIR program. +// It handles parsing of command line arguments, and it invokes an entry-point function exposed by the QIR program. +//---------------------------------------------------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "CLI11.hpp" + +#include "QirRuntime.hpp" +#include "QirContext.hpp" + +#include "SimFactory.hpp" + +using namespace Microsoft::Quantum; +using namespace std; + +// Auxiliary functions for interop with Q# Array type. +struct InteropArray +{ + int64_t Size; + void* Data; + + InteropArray(int64_t size, void* data) : + Size(size), + Data(data){} +}; + +template +unique_ptr CreateInteropArray(vector& v) +{ + unique_ptr array(new InteropArray(v.size(), v.data())); + return array; +} + +template +void TranslateVector(vector& sourceVector, vector& destinationVector, function translationFunction) +{ + destinationVector.resize(sourceVector.size()); + transform(sourceVector.begin(), sourceVector.end(), destinationVector.begin(), translationFunction); +} + +extern "C" void UseDoubleArrayArg( + InteropArray* DoubleArrayArg +); // QIR interop function. + +int main(int argc, char* argv[]) +{ + CLI::App app("QIR Standalone Entry Point"); + + // Initialize runtime. + unique_ptr sim = CreateFullstateSimulator(); + QirContextScope qirctx(sim.get(), false /*trackAllocatedObjects*/); + + // Add the --simulation-output option. + string simulationOutputFile; + CLI::Option* simulationOutputFileOpt = app.add_option( + "--simulation-output", + simulationOutputFile, + "File where the output produced during the simulation is written"); + + // Add a command line option for each entry-point parameter. + vector DoubleArrayArgCli; + app.add_option("--DoubleArrayArg", DoubleArrayArgCli, "Option to provide a value for the DoubleArrayArg parameter") + ->required(); + + // After all the options have been added, parse arguments from the command line. + CLI11_PARSE(app, argc, argv); + + // Cast parsed arguments to its interop types. + unique_ptr DoubleArrayArgUniquePtr = CreateInteropArray(DoubleArrayArgCli); + InteropArray* DoubleArrayArgInterop = DoubleArrayArgUniquePtr.get(); + + // Redirect the simulator output from std::cout if the --simulation-output option is present. + ostream* simulatorOutputStream = &cout; + ofstream simulationOutputFileStream; + if (!simulationOutputFileOpt->empty()) + { + simulationOutputFileStream.open(simulationOutputFile); + SetOutputStream(simulationOutputFileStream); + simulatorOutputStream = &simulationOutputFileStream; + } + + // Execute the entry point operation. + UseDoubleArrayArg( + DoubleArrayArgInterop + ); + + // Flush the output of the simulation. + simulatorOutputStream->flush(); + if (simulationOutputFileStream.is_open()) + { + simulationOutputFileStream.close(); + } + + return 0; +} diff --git a/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseIntegerArg.cpp b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseIntegerArg.cpp new file mode 100644 index 00000000000..57e6493971f --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseIntegerArg.cpp @@ -0,0 +1,78 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// This code was generated by the Microsoft.Quantum.Qir.Tools package. +// The purpose of this source code file is to provide an entry-point for executing a QIR program. +// It handles parsing of command line arguments, and it invokes an entry-point function exposed by the QIR program. +//---------------------------------------------------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "CLI11.hpp" + +#include "QirRuntime.hpp" +#include "QirContext.hpp" + +#include "SimFactory.hpp" + +using namespace Microsoft::Quantum; +using namespace std; + +extern "C" void UseIntegerArg( + int64_t IntegerArg +); // QIR interop function. + +int main(int argc, char* argv[]) +{ + CLI::App app("QIR Standalone Entry Point"); + + // Initialize runtime. + unique_ptr sim = CreateFullstateSimulator(); + QirContextScope qirctx(sim.get(), false /*trackAllocatedObjects*/); + + // Add the --simulation-output option. + string simulationOutputFile; + CLI::Option* simulationOutputFileOpt = app.add_option( + "--simulation-output", + simulationOutputFile, + "File where the output produced during the simulation is written"); + + // Add a command line option for each entry-point parameter. + int64_t IntegerArgCli; + IntegerArgCli = 0; + app.add_option("--IntegerArg", IntegerArgCli, "Option to provide a value for the IntegerArg parameter") + ->required(); + + // After all the options have been added, parse arguments from the command line. + CLI11_PARSE(app, argc, argv); + + // Cast parsed arguments to its interop types. + int64_t IntegerArgInterop = IntegerArgCli; + + // Redirect the simulator output from std::cout if the --simulation-output option is present. + ostream* simulatorOutputStream = &cout; + ofstream simulationOutputFileStream; + if (!simulationOutputFileOpt->empty()) + { + simulationOutputFileStream.open(simulationOutputFile); + SetOutputStream(simulationOutputFileStream); + simulatorOutputStream = &simulationOutputFileStream; + } + + // Execute the entry point operation. + UseIntegerArg( + IntegerArgInterop + ); + + // Flush the output of the simulation. + simulatorOutputStream->flush(); + if (simulationOutputFileStream.is_open()) + { + simulationOutputFileStream.close(); + } + + return 0; +} diff --git a/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseIntegerArrayArg.cpp b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseIntegerArrayArg.cpp new file mode 100644 index 00000000000..5445c51b136 --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseIntegerArrayArg.cpp @@ -0,0 +1,103 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// This code was generated by the Microsoft.Quantum.Qir.Tools package. +// The purpose of this source code file is to provide an entry-point for executing a QIR program. +// It handles parsing of command line arguments, and it invokes an entry-point function exposed by the QIR program. +//---------------------------------------------------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "CLI11.hpp" + +#include "QirRuntime.hpp" +#include "QirContext.hpp" + +#include "SimFactory.hpp" + +using namespace Microsoft::Quantum; +using namespace std; + +// Auxiliary functions for interop with Q# Array type. +struct InteropArray +{ + int64_t Size; + void* Data; + + InteropArray(int64_t size, void* data) : + Size(size), + Data(data){} +}; + +template +unique_ptr CreateInteropArray(vector& v) +{ + unique_ptr array(new InteropArray(v.size(), v.data())); + return array; +} + +template +void TranslateVector(vector& sourceVector, vector& destinationVector, function translationFunction) +{ + destinationVector.resize(sourceVector.size()); + transform(sourceVector.begin(), sourceVector.end(), destinationVector.begin(), translationFunction); +} + +extern "C" void UseIntegerArrayArg( + InteropArray* IntegerArrayArg +); // QIR interop function. + +int main(int argc, char* argv[]) +{ + CLI::App app("QIR Standalone Entry Point"); + + // Initialize runtime. + unique_ptr sim = CreateFullstateSimulator(); + QirContextScope qirctx(sim.get(), false /*trackAllocatedObjects*/); + + // Add the --simulation-output option. + string simulationOutputFile; + CLI::Option* simulationOutputFileOpt = app.add_option( + "--simulation-output", + simulationOutputFile, + "File where the output produced during the simulation is written"); + + // Add a command line option for each entry-point parameter. + vector IntegerArrayArgCli; + app.add_option("--IntegerArrayArg", IntegerArrayArgCli, "Option to provide a value for the IntegerArrayArg parameter") + ->required(); + + // After all the options have been added, parse arguments from the command line. + CLI11_PARSE(app, argc, argv); + + // Cast parsed arguments to its interop types. + unique_ptr IntegerArrayArgUniquePtr = CreateInteropArray(IntegerArrayArgCli); + InteropArray* IntegerArrayArgInterop = IntegerArrayArgUniquePtr.get(); + + // Redirect the simulator output from std::cout if the --simulation-output option is present. + ostream* simulatorOutputStream = &cout; + ofstream simulationOutputFileStream; + if (!simulationOutputFileOpt->empty()) + { + simulationOutputFileStream.open(simulationOutputFile); + SetOutputStream(simulationOutputFileStream); + simulatorOutputStream = &simulationOutputFileStream; + } + + // Execute the entry point operation. + UseIntegerArrayArg( + IntegerArrayArgInterop + ); + + // Flush the output of the simulation. + simulatorOutputStream->flush(); + if (simulationOutputFileStream.is_open()) + { + simulationOutputFileStream.close(); + } + + return 0; +} diff --git a/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseMiscArgs.cpp b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseMiscArgs.cpp new file mode 100644 index 00000000000..f8246aca5b0 --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseMiscArgs.cpp @@ -0,0 +1,176 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// This code was generated by the Microsoft.Quantum.Qir.Tools package. +// The purpose of this source code file is to provide an entry-point for executing a QIR program. +// It handles parsing of command line arguments, and it invokes an entry-point function exposed by the QIR program. +//---------------------------------------------------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "CLI11.hpp" + +#include "QirRuntime.hpp" +#include "QirContext.hpp" + +#include "SimFactory.hpp" + +using namespace Microsoft::Quantum; +using namespace std; + +// Auxiliary functions for interop with Q# Array type. +struct InteropArray +{ + int64_t Size; + void* Data; + + InteropArray(int64_t size, void* data) : + Size(size), + Data(data){} +}; + +template +unique_ptr CreateInteropArray(vector& v) +{ + unique_ptr array(new InteropArray(v.size(), v.data())); + return array; +} + +template +void TranslateVector(vector& sourceVector, vector& destinationVector, function translationFunction) +{ + destinationVector.resize(sourceVector.size()); + transform(sourceVector.begin(), sourceVector.end(), destinationVector.begin(), translationFunction); +} + +// Auxiliary functions for interop with Q# Range type. +using RangeTuple = tuple; +struct InteropRange +{ + int64_t Start; + int64_t Step; + int64_t End; + + InteropRange() : + Start(0), + Step(0), + End(0){} + + InteropRange(RangeTuple rangeTuple) : + Start(get<0>(rangeTuple)), + Step(get<1>(rangeTuple)), + End(get<2>(rangeTuple)){} +}; + +unique_ptr CreateInteropRange(RangeTuple rangeTuple) +{ + unique_ptr range(new InteropRange(rangeTuple)); + return range; +} + +InteropRange* TranslateRangeTupleToInteropRangePointer(RangeTuple& rangeTuple) +{ + InteropRange* range = new InteropRange(rangeTuple); + return range; +} + +// Auxiliary functions for interop with Q# Bool type. +const char InteropFalseAsChar = 0x0; +const char InteropTrueAsChar = 0x1; +map BoolAsCharMap{ + {"0", InteropFalseAsChar}, + {"false", InteropFalseAsChar}, + {"1", InteropTrueAsChar}, + {"true", InteropTrueAsChar} +}; + +// Auxiliary functions for interop with Q# String type. +const char* TranslateStringToCharBuffer(string& s) +{ + return s.c_str(); +} + +extern "C" void UseMiscArgs( + char BoolArg, + InteropArray* IntegerArrayArg, + InteropRange* RangeArg, + const char* StringArg +); // QIR interop function. + +int main(int argc, char* argv[]) +{ + CLI::App app("QIR Standalone Entry Point"); + + // Initialize runtime. + unique_ptr sim = CreateFullstateSimulator(); + QirContextScope qirctx(sim.get(), false /*trackAllocatedObjects*/); + + // Add the --simulation-output option. + string simulationOutputFile; + CLI::Option* simulationOutputFileOpt = app.add_option( + "--simulation-output", + simulationOutputFile, + "File where the output produced during the simulation is written"); + + // Add a command line option for each entry-point parameter. + char BoolArgCli; + BoolArgCli = InteropFalseAsChar; + app.add_option("--BoolArg", BoolArgCli, "Option to provide a value for the BoolArg parameter") + ->required() + ->transform(CLI::CheckedTransformer(BoolAsCharMap, CLI::ignore_case)); + + vector IntegerArrayArgCli; + app.add_option("--IntegerArrayArg", IntegerArrayArgCli, "Option to provide a value for the IntegerArrayArg parameter") + ->required(); + + RangeTuple RangeArgCli; + app.add_option("--RangeArg", RangeArgCli, "Option to provide a value for the RangeArg parameter") + ->required(); + + string StringArgCli; + app.add_option("--StringArg", StringArgCli, "Option to provide a value for the StringArg parameter") + ->required(); + + // After all the options have been added, parse arguments from the command line. + CLI11_PARSE(app, argc, argv); + + // Cast parsed arguments to its interop types. + char BoolArgInterop = BoolArgCli; + + unique_ptr IntegerArrayArgUniquePtr = CreateInteropArray(IntegerArrayArgCli); + InteropArray* IntegerArrayArgInterop = IntegerArrayArgUniquePtr.get(); + + InteropRange* RangeArgInterop = TranslateRangeTupleToInteropRangePointer(RangeArgCli); + + const char* StringArgInterop = TranslateStringToCharBuffer(StringArgCli); + + // Redirect the simulator output from std::cout if the --simulation-output option is present. + ostream* simulatorOutputStream = &cout; + ofstream simulationOutputFileStream; + if (!simulationOutputFileOpt->empty()) + { + simulationOutputFileStream.open(simulationOutputFile); + SetOutputStream(simulationOutputFileStream); + simulatorOutputStream = &simulationOutputFileStream; + } + + // Execute the entry point operation. + UseMiscArgs( + BoolArgInterop, + IntegerArrayArgInterop, + RangeArgInterop, + StringArgInterop + ); + + // Flush the output of the simulation. + simulatorOutputStream->flush(); + if (simulationOutputFileStream.is_open()) + { + simulationOutputFileStream.close(); + } + + return 0; +} diff --git a/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseNoArgs.cpp b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseNoArgs.cpp new file mode 100644 index 00000000000..b4d70bf263b --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseNoArgs.cpp @@ -0,0 +1,67 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// This code was generated by the Microsoft.Quantum.Qir.Tools package. +// The purpose of this source code file is to provide an entry-point for executing a QIR program. +// It handles parsing of command line arguments, and it invokes an entry-point function exposed by the QIR program. +//---------------------------------------------------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "CLI11.hpp" + +#include "QirRuntime.hpp" +#include "QirContext.hpp" + +#include "SimFactory.hpp" + +using namespace Microsoft::Quantum; +using namespace std; + +extern "C" void UseNoArgs( +); // QIR interop function. + +int main(int argc, char* argv[]) +{ + CLI::App app("QIR Standalone Entry Point"); + + // Initialize runtime. + unique_ptr sim = CreateFullstateSimulator(); + QirContextScope qirctx(sim.get(), false /*trackAllocatedObjects*/); + + // Add the --simulation-output option. + string simulationOutputFile; + CLI::Option* simulationOutputFileOpt = app.add_option( + "--simulation-output", + simulationOutputFile, + "File where the output produced during the simulation is written"); + + // After all the options have been added, parse arguments from the command line. + CLI11_PARSE(app, argc, argv); + + // Redirect the simulator output from std::cout if the --simulation-output option is present. + ostream* simulatorOutputStream = &cout; + ofstream simulationOutputFileStream; + if (!simulationOutputFileOpt->empty()) + { + simulationOutputFileStream.open(simulationOutputFile); + SetOutputStream(simulationOutputFileStream); + simulatorOutputStream = &simulationOutputFileStream; + } + + // Execute the entry point operation. + UseNoArgs( + ); + + // Flush the output of the simulation. + simulatorOutputStream->flush(); + if (simulationOutputFileStream.is_open()) + { + simulationOutputFileStream.close(); + } + + return 0; +} diff --git a/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UsePauliArg.cpp b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UsePauliArg.cpp new file mode 100644 index 00000000000..f512ea18504 --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UsePauliArg.cpp @@ -0,0 +1,92 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// This code was generated by the Microsoft.Quantum.Qir.Tools package. +// The purpose of this source code file is to provide an entry-point for executing a QIR program. +// It handles parsing of command line arguments, and it invokes an entry-point function exposed by the QIR program. +//---------------------------------------------------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "CLI11.hpp" + +#include "QirRuntime.hpp" +#include "QirContext.hpp" + +#include "SimFactory.hpp" + +using namespace Microsoft::Quantum; +using namespace std; + +// Auxiliary functions for interop with Q# Pauli type. +map PauliMap{ + {"PauliI", PauliId::PauliId_I}, + {"PauliX", PauliId::PauliId_X}, + {"PauliY", PauliId::PauliId_Y}, + {"PauliZ", PauliId::PauliId_Z} +}; + +char TranslatePauliToChar(PauliId& pauli) +{ + return static_cast(pauli); +} + +extern "C" void UsePauliArg( + char PauliArg +); // QIR interop function. + +int main(int argc, char* argv[]) +{ + CLI::App app("QIR Standalone Entry Point"); + + // Initialize runtime. + unique_ptr sim = CreateFullstateSimulator(); + QirContextScope qirctx(sim.get(), false /*trackAllocatedObjects*/); + + // Add the --simulation-output option. + string simulationOutputFile; + CLI::Option* simulationOutputFileOpt = app.add_option( + "--simulation-output", + simulationOutputFile, + "File where the output produced during the simulation is written"); + + // Add a command line option for each entry-point parameter. + PauliId PauliArgCli; + PauliArgCli = PauliId::PauliId_I; + app.add_option("--PauliArg", PauliArgCli, "Option to provide a value for the PauliArg parameter") + ->required() + ->transform(CLI::CheckedTransformer(PauliMap, CLI::ignore_case)); + + // After all the options have been added, parse arguments from the command line. + CLI11_PARSE(app, argc, argv); + + // Cast parsed arguments to its interop types. + char PauliArgInterop = TranslatePauliToChar(PauliArgCli); + + // Redirect the simulator output from std::cout if the --simulation-output option is present. + ostream* simulatorOutputStream = &cout; + ofstream simulationOutputFileStream; + if (!simulationOutputFileOpt->empty()) + { + simulationOutputFileStream.open(simulationOutputFile); + SetOutputStream(simulationOutputFileStream); + simulatorOutputStream = &simulationOutputFileStream; + } + + // Execute the entry point operation. + UsePauliArg( + PauliArgInterop + ); + + // Flush the output of the simulation. + simulatorOutputStream->flush(); + if (simulationOutputFileStream.is_open()) + { + simulationOutputFileStream.close(); + } + + return 0; +} diff --git a/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UsePauliArrayArg.cpp b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UsePauliArrayArg.cpp new file mode 100644 index 00000000000..e8a18e06d0d --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UsePauliArrayArg.cpp @@ -0,0 +1,119 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// This code was generated by the Microsoft.Quantum.Qir.Tools package. +// The purpose of this source code file is to provide an entry-point for executing a QIR program. +// It handles parsing of command line arguments, and it invokes an entry-point function exposed by the QIR program. +//---------------------------------------------------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "CLI11.hpp" + +#include "QirRuntime.hpp" +#include "QirContext.hpp" + +#include "SimFactory.hpp" + +using namespace Microsoft::Quantum; +using namespace std; + +// Auxiliary functions for interop with Q# Array type. +struct InteropArray +{ + int64_t Size; + void* Data; + + InteropArray(int64_t size, void* data) : + Size(size), + Data(data){} +}; + +template +unique_ptr CreateInteropArray(vector& v) +{ + unique_ptr array(new InteropArray(v.size(), v.data())); + return array; +} + +template +void TranslateVector(vector& sourceVector, vector& destinationVector, function translationFunction) +{ + destinationVector.resize(sourceVector.size()); + transform(sourceVector.begin(), sourceVector.end(), destinationVector.begin(), translationFunction); +} + +// Auxiliary functions for interop with Q# Pauli type. +map PauliMap{ + {"PauliI", PauliId::PauliId_I}, + {"PauliX", PauliId::PauliId_X}, + {"PauliY", PauliId::PauliId_Y}, + {"PauliZ", PauliId::PauliId_Z} +}; + +char TranslatePauliToChar(PauliId& pauli) +{ + return static_cast(pauli); +} + +extern "C" void UsePauliArrayArg( + InteropArray* PauliArrayArg +); // QIR interop function. + +int main(int argc, char* argv[]) +{ + CLI::App app("QIR Standalone Entry Point"); + + // Initialize runtime. + unique_ptr sim = CreateFullstateSimulator(); + QirContextScope qirctx(sim.get(), false /*trackAllocatedObjects*/); + + // Add the --simulation-output option. + string simulationOutputFile; + CLI::Option* simulationOutputFileOpt = app.add_option( + "--simulation-output", + simulationOutputFile, + "File where the output produced during the simulation is written"); + + // Add a command line option for each entry-point parameter. + vector PauliArrayArgCli; + app.add_option("--PauliArrayArg", PauliArrayArgCli, "Option to provide a value for the PauliArrayArg parameter") + ->required() + ->transform(CLI::CheckedTransformer(PauliMap, CLI::ignore_case)); + + // After all the options have been added, parse arguments from the command line. + CLI11_PARSE(app, argc, argv); + + // Cast parsed arguments to its interop types. + vector PauliArrayArgIntermediate; + TranslateVector(PauliArrayArgCli, PauliArrayArgIntermediate, TranslatePauliToChar); + unique_ptr PauliArrayArgUniquePtr = CreateInteropArray(PauliArrayArgIntermediate); + InteropArray* PauliArrayArgInterop = PauliArrayArgUniquePtr.get(); + + // Redirect the simulator output from std::cout if the --simulation-output option is present. + ostream* simulatorOutputStream = &cout; + ofstream simulationOutputFileStream; + if (!simulationOutputFileOpt->empty()) + { + simulationOutputFileStream.open(simulationOutputFile); + SetOutputStream(simulationOutputFileStream); + simulatorOutputStream = &simulationOutputFileStream; + } + + // Execute the entry point operation. + UsePauliArrayArg( + PauliArrayArgInterop + ); + + // Flush the output of the simulation. + simulatorOutputStream->flush(); + if (simulationOutputFileStream.is_open()) + { + simulationOutputFileStream.close(); + } + + return 0; +} diff --git a/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseRangeArg.cpp b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseRangeArg.cpp new file mode 100644 index 00000000000..c0fae41a14f --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseRangeArg.cpp @@ -0,0 +1,108 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// This code was generated by the Microsoft.Quantum.Qir.Tools package. +// The purpose of this source code file is to provide an entry-point for executing a QIR program. +// It handles parsing of command line arguments, and it invokes an entry-point function exposed by the QIR program. +//---------------------------------------------------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "CLI11.hpp" + +#include "QirRuntime.hpp" +#include "QirContext.hpp" + +#include "SimFactory.hpp" + +using namespace Microsoft::Quantum; +using namespace std; + +// Auxiliary functions for interop with Q# Range type. +using RangeTuple = tuple; +struct InteropRange +{ + int64_t Start; + int64_t Step; + int64_t End; + + InteropRange() : + Start(0), + Step(0), + End(0){} + + InteropRange(RangeTuple rangeTuple) : + Start(get<0>(rangeTuple)), + Step(get<1>(rangeTuple)), + End(get<2>(rangeTuple)){} +}; + +unique_ptr CreateInteropRange(RangeTuple rangeTuple) +{ + unique_ptr range(new InteropRange(rangeTuple)); + return range; +} + +InteropRange* TranslateRangeTupleToInteropRangePointer(RangeTuple& rangeTuple) +{ + InteropRange* range = new InteropRange(rangeTuple); + return range; +} + +extern "C" void UseRangeArg( + InteropRange* RangeArg +); // QIR interop function. + +int main(int argc, char* argv[]) +{ + CLI::App app("QIR Standalone Entry Point"); + + // Initialize runtime. + unique_ptr sim = CreateFullstateSimulator(); + QirContextScope qirctx(sim.get(), false /*trackAllocatedObjects*/); + + // Add the --simulation-output option. + string simulationOutputFile; + CLI::Option* simulationOutputFileOpt = app.add_option( + "--simulation-output", + simulationOutputFile, + "File where the output produced during the simulation is written"); + + // Add a command line option for each entry-point parameter. + RangeTuple RangeArgCli; + app.add_option("--RangeArg", RangeArgCli, "Option to provide a value for the RangeArg parameter") + ->required(); + + // After all the options have been added, parse arguments from the command line. + CLI11_PARSE(app, argc, argv); + + // Cast parsed arguments to its interop types. + InteropRange* RangeArgInterop = TranslateRangeTupleToInteropRangePointer(RangeArgCli); + + // Redirect the simulator output from std::cout if the --simulation-output option is present. + ostream* simulatorOutputStream = &cout; + ofstream simulationOutputFileStream; + if (!simulationOutputFileOpt->empty()) + { + simulationOutputFileStream.open(simulationOutputFile); + SetOutputStream(simulationOutputFileStream); + simulatorOutputStream = &simulationOutputFileStream; + } + + // Execute the entry point operation. + UseRangeArg( + RangeArgInterop + ); + + // Flush the output of the simulation. + simulatorOutputStream->flush(); + if (simulationOutputFileStream.is_open()) + { + simulationOutputFileStream.close(); + } + + return 0; +} diff --git a/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseRangeArrayArg.cpp b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseRangeArrayArg.cpp new file mode 100644 index 00000000000..aae5780698a --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseRangeArrayArg.cpp @@ -0,0 +1,146 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// This code was generated by the Microsoft.Quantum.Qir.Tools package. +// The purpose of this source code file is to provide an entry-point for executing a QIR program. +// It handles parsing of command line arguments, and it invokes an entry-point function exposed by the QIR program. +//---------------------------------------------------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "CLI11.hpp" + +#include "QirRuntime.hpp" +#include "QirContext.hpp" + +#include "SimFactory.hpp" + +using namespace Microsoft::Quantum; +using namespace std; + +// Auxiliary functions for interop with Q# Array type. +struct InteropArray +{ + int64_t Size; + void* Data; + + InteropArray(int64_t size, void* data) : + Size(size), + Data(data){} +}; + +template +unique_ptr CreateInteropArray(vector& v) +{ + unique_ptr array(new InteropArray(v.size(), v.data())); + return array; +} + +template +void TranslateVector(vector& sourceVector, vector& destinationVector, function translationFunction) +{ + destinationVector.resize(sourceVector.size()); + transform(sourceVector.begin(), sourceVector.end(), destinationVector.begin(), translationFunction); +} + +// Auxiliary functions for interop with Q# Range type. +using RangeTuple = tuple; +struct InteropRange +{ + int64_t Start; + int64_t Step; + int64_t End; + + InteropRange() : + Start(0), + Step(0), + End(0){} + + InteropRange(RangeTuple rangeTuple) : + Start(get<0>(rangeTuple)), + Step(get<1>(rangeTuple)), + End(get<2>(rangeTuple)){} +}; + +unique_ptr CreateInteropRange(RangeTuple rangeTuple) +{ + unique_ptr range(new InteropRange(rangeTuple)); + return range; +} + +InteropRange* TranslateRangeTupleToInteropRangePointer(RangeTuple& rangeTuple) +{ + InteropRange* range = new InteropRange(rangeTuple); + return range; +} + +// Auxiliary functions for interop with Q# Range[] type +template +void FreePointerVector(vector& v) +{ + for (auto p : v) + { + delete p; + } +} + +extern "C" void UseRangeArrayArg( + InteropArray* RangeArrayArg +); // QIR interop function. + +int main(int argc, char* argv[]) +{ + CLI::App app("QIR Standalone Entry Point"); + + // Initialize runtime. + unique_ptr sim = CreateFullstateSimulator(); + QirContextScope qirctx(sim.get(), false /*trackAllocatedObjects*/); + + // Add the --simulation-output option. + string simulationOutputFile; + CLI::Option* simulationOutputFileOpt = app.add_option( + "--simulation-output", + simulationOutputFile, + "File where the output produced during the simulation is written"); + + // Add a command line option for each entry-point parameter. + vector RangeArrayArgCli; + app.add_option("--RangeArrayArg", RangeArrayArgCli, "Option to provide a value for the RangeArrayArg parameter") + ->required(); + + // After all the options have been added, parse arguments from the command line. + CLI11_PARSE(app, argc, argv); + + // Cast parsed arguments to its interop types. + vector RangeArrayArgIntermediate; + TranslateVector(RangeArrayArgCli, RangeArrayArgIntermediate, TranslateRangeTupleToInteropRangePointer); + unique_ptr RangeArrayArgUniquePtr = CreateInteropArray(RangeArrayArgIntermediate); + InteropArray* RangeArrayArgInterop = RangeArrayArgUniquePtr.get(); + + // Redirect the simulator output from std::cout if the --simulation-output option is present. + ostream* simulatorOutputStream = &cout; + ofstream simulationOutputFileStream; + if (!simulationOutputFileOpt->empty()) + { + simulationOutputFileStream.open(simulationOutputFile); + SetOutputStream(simulationOutputFileStream); + simulatorOutputStream = &simulationOutputFileStream; + } + + // Execute the entry point operation. + UseRangeArrayArg( + RangeArrayArgInterop + ); + + // Flush the output of the simulation. + simulatorOutputStream->flush(); + if (simulationOutputFileStream.is_open()) + { + simulationOutputFileStream.close(); + } + + return 0; +} diff --git a/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseResultArg.cpp b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseResultArg.cpp new file mode 100644 index 00000000000..c3fa773f201 --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseResultArg.cpp @@ -0,0 +1,89 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// This code was generated by the Microsoft.Quantum.Qir.Tools package. +// The purpose of this source code file is to provide an entry-point for executing a QIR program. +// It handles parsing of command line arguments, and it invokes an entry-point function exposed by the QIR program. +//---------------------------------------------------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "CLI11.hpp" + +#include "QirRuntime.hpp" +#include "QirContext.hpp" + +#include "SimFactory.hpp" + +using namespace Microsoft::Quantum; +using namespace std; + +// Auxiliary functions for interop with Q# Result type. +const char InteropResultZeroAsChar = 0x0; +const char InteropResultOneAsChar = 0x1; +map ResultAsCharMap{ + {"0", InteropResultZeroAsChar}, + {"Zero", InteropResultZeroAsChar}, + {"1", InteropResultOneAsChar}, + {"One", InteropResultOneAsChar} +}; + +extern "C" void UseResultArg( + char ResultArg +); // QIR interop function. + +int main(int argc, char* argv[]) +{ + CLI::App app("QIR Standalone Entry Point"); + + // Initialize runtime. + unique_ptr sim = CreateFullstateSimulator(); + QirContextScope qirctx(sim.get(), false /*trackAllocatedObjects*/); + + // Add the --simulation-output option. + string simulationOutputFile; + CLI::Option* simulationOutputFileOpt = app.add_option( + "--simulation-output", + simulationOutputFile, + "File where the output produced during the simulation is written"); + + // Add a command line option for each entry-point parameter. + char ResultArgCli; + ResultArgCli = InteropResultZeroAsChar; + app.add_option("--ResultArg", ResultArgCli, "Option to provide a value for the ResultArg parameter") + ->required() + ->transform(CLI::CheckedTransformer(ResultAsCharMap, CLI::ignore_case)); + + // After all the options have been added, parse arguments from the command line. + CLI11_PARSE(app, argc, argv); + + // Cast parsed arguments to its interop types. + char ResultArgInterop = ResultArgCli; + + // Redirect the simulator output from std::cout if the --simulation-output option is present. + ostream* simulatorOutputStream = &cout; + ofstream simulationOutputFileStream; + if (!simulationOutputFileOpt->empty()) + { + simulationOutputFileStream.open(simulationOutputFile); + SetOutputStream(simulationOutputFileStream); + simulatorOutputStream = &simulationOutputFileStream; + } + + // Execute the entry point operation. + UseResultArg( + ResultArgInterop + ); + + // Flush the output of the simulation. + simulatorOutputStream->flush(); + if (simulationOutputFileStream.is_open()) + { + simulationOutputFileStream.close(); + } + + return 0; +} diff --git a/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseResultArrayArg.cpp b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseResultArrayArg.cpp new file mode 100644 index 00000000000..7d1b992b905 --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseResultArrayArg.cpp @@ -0,0 +1,114 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// This code was generated by the Microsoft.Quantum.Qir.Tools package. +// The purpose of this source code file is to provide an entry-point for executing a QIR program. +// It handles parsing of command line arguments, and it invokes an entry-point function exposed by the QIR program. +//---------------------------------------------------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "CLI11.hpp" + +#include "QirRuntime.hpp" +#include "QirContext.hpp" + +#include "SimFactory.hpp" + +using namespace Microsoft::Quantum; +using namespace std; + +// Auxiliary functions for interop with Q# Array type. +struct InteropArray +{ + int64_t Size; + void* Data; + + InteropArray(int64_t size, void* data) : + Size(size), + Data(data){} +}; + +template +unique_ptr CreateInteropArray(vector& v) +{ + unique_ptr array(new InteropArray(v.size(), v.data())); + return array; +} + +template +void TranslateVector(vector& sourceVector, vector& destinationVector, function translationFunction) +{ + destinationVector.resize(sourceVector.size()); + transform(sourceVector.begin(), sourceVector.end(), destinationVector.begin(), translationFunction); +} + +// Auxiliary functions for interop with Q# Result type. +const char InteropResultZeroAsChar = 0x0; +const char InteropResultOneAsChar = 0x1; +map ResultAsCharMap{ + {"0", InteropResultZeroAsChar}, + {"Zero", InteropResultZeroAsChar}, + {"1", InteropResultOneAsChar}, + {"One", InteropResultOneAsChar} +}; + +extern "C" void UseResultArrayArg( + InteropArray* ResultArrayArg +); // QIR interop function. + +int main(int argc, char* argv[]) +{ + CLI::App app("QIR Standalone Entry Point"); + + // Initialize runtime. + unique_ptr sim = CreateFullstateSimulator(); + QirContextScope qirctx(sim.get(), false /*trackAllocatedObjects*/); + + // Add the --simulation-output option. + string simulationOutputFile; + CLI::Option* simulationOutputFileOpt = app.add_option( + "--simulation-output", + simulationOutputFile, + "File where the output produced during the simulation is written"); + + // Add a command line option for each entry-point parameter. + vector ResultArrayArgCli; + app.add_option("--ResultArrayArg", ResultArrayArgCli, "Option to provide a value for the ResultArrayArg parameter") + ->required() + ->transform(CLI::CheckedTransformer(ResultAsCharMap, CLI::ignore_case)); + + // After all the options have been added, parse arguments from the command line. + CLI11_PARSE(app, argc, argv); + + // Cast parsed arguments to its interop types. + unique_ptr ResultArrayArgUniquePtr = CreateInteropArray(ResultArrayArgCli); + InteropArray* ResultArrayArgInterop = ResultArrayArgUniquePtr.get(); + + // Redirect the simulator output from std::cout if the --simulation-output option is present. + ostream* simulatorOutputStream = &cout; + ofstream simulationOutputFileStream; + if (!simulationOutputFileOpt->empty()) + { + simulationOutputFileStream.open(simulationOutputFile); + SetOutputStream(simulationOutputFileStream); + simulatorOutputStream = &simulationOutputFileStream; + } + + // Execute the entry point operation. + UseResultArrayArg( + ResultArrayArgInterop + ); + + // Flush the output of the simulation. + simulatorOutputStream->flush(); + if (simulationOutputFileStream.is_open()) + { + simulationOutputFileStream.close(); + } + + return 0; +} diff --git a/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseStringArg.cpp b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseStringArg.cpp new file mode 100644 index 00000000000..3063ea96f06 --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/TestCases/FullStateDriverGenerator/UseStringArg.cpp @@ -0,0 +1,83 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// This code was generated by the Microsoft.Quantum.Qir.Tools package. +// The purpose of this source code file is to provide an entry-point for executing a QIR program. +// It handles parsing of command line arguments, and it invokes an entry-point function exposed by the QIR program. +//---------------------------------------------------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "CLI11.hpp" + +#include "QirRuntime.hpp" +#include "QirContext.hpp" + +#include "SimFactory.hpp" + +using namespace Microsoft::Quantum; +using namespace std; + +// Auxiliary functions for interop with Q# String type. +const char* TranslateStringToCharBuffer(string& s) +{ + return s.c_str(); +} + +extern "C" void UseStringArg( + const char* StringArg +); // QIR interop function. + +int main(int argc, char* argv[]) +{ + CLI::App app("QIR Standalone Entry Point"); + + // Initialize runtime. + unique_ptr sim = CreateFullstateSimulator(); + QirContextScope qirctx(sim.get(), false /*trackAllocatedObjects*/); + + // Add the --simulation-output option. + string simulationOutputFile; + CLI::Option* simulationOutputFileOpt = app.add_option( + "--simulation-output", + simulationOutputFile, + "File where the output produced during the simulation is written"); + + // Add a command line option for each entry-point parameter. + string StringArgCli; + app.add_option("--StringArg", StringArgCli, "Option to provide a value for the StringArg parameter") + ->required(); + + // After all the options have been added, parse arguments from the command line. + CLI11_PARSE(app, argc, argv); + + // Cast parsed arguments to its interop types. + const char* StringArgInterop = TranslateStringToCharBuffer(StringArgCli); + + // Redirect the simulator output from std::cout if the --simulation-output option is present. + ostream* simulatorOutputStream = &cout; + ofstream simulationOutputFileStream; + if (!simulationOutputFileOpt->empty()) + { + simulationOutputFileStream.open(simulationOutputFile); + SetOutputStream(simulationOutputFileStream); + simulatorOutputStream = &simulationOutputFileStream; + } + + // Execute the entry point operation. + UseStringArg( + StringArgInterop + ); + + // Flush the output of the simulation. + simulatorOutputStream->flush(); + if (simulationOutputFileStream.is_open()) + { + simulationOutputFileStream.close(); + } + + return 0; +} diff --git a/src/Qir/Execution/Tools.Tests/Tests.Microsoft.Quantum.Qir.Tools.csproj b/src/Qir/Execution/Tools.Tests/Tests.Microsoft.Quantum.Qir.Tools.csproj new file mode 100644 index 00000000000..cd9a3d60202 --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/Tests.Microsoft.Quantum.Qir.Tools.csproj @@ -0,0 +1,68 @@ + + + + netcoreapp3.1 + false + + + + + + + + + + + + + + + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + + diff --git a/src/Qir/Execution/Tools.Tests/Util.cs b/src/Qir/Execution/Tools.Tests/Util.cs new file mode 100644 index 00000000000..e5e81c30241 --- /dev/null +++ b/src/Qir/Execution/Tools.Tests/Util.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.IO; + +namespace Tests.Microsoft.Quantum.Qir.Tools +{ + internal static class Util + { + public static bool CompareFiles(FileInfo fileA, FileInfo fileB) + { + if (fileA.FullName == fileB.FullName) + { + return true; + } + + using var fileStreamA = fileA.OpenRead(); + using var fileStreamB = fileB.OpenRead(); + return CompareStreams(fileStreamA, fileStreamB); + } + + public static bool CompareStreams(Stream streamA, Stream streamB) + { + if (streamA.Length != streamB.Length) + { + return false; + } + + (streamA.Position, streamB.Position) = (0, 0); + (int byteA, int byteB) = (0, 0); + while ((byteA == byteB) && (byteA != -1)) + { + (byteA, byteB) = (streamA.ReadByte(), streamB.ReadByte()); + } + + return byteA == byteB; + } + + public static FileInfo CreateBinaryFile(DirectoryInfo directory, string fileName, byte[] contents) + { + var fileInfo = new FileInfo(Path.Combine(directory.FullName, fileName)); + File.WriteAllBytes(fileInfo.FullName, contents); + return fileInfo; + } + + public static FileInfo CreateTextFile(DirectoryInfo directory, string fileName, string contents) + { + var fileInfo = new FileInfo(Path.Combine(directory.FullName, fileName)); + File.WriteAllText(fileInfo.FullName, contents); + return fileInfo; + } + } +} diff --git a/src/Qir/Execution/Tools/AssemblyInfo.cs b/src/Qir/Execution/Tools/AssemblyInfo.cs new file mode 100644 index 00000000000..1a412a83263 --- /dev/null +++ b/src/Qir/Execution/Tools/AssemblyInfo.cs @@ -0,0 +1,23 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// In SDK-style projects such as this one, several assembly attributes that were historically +// defined in this file are now automatically added during build and populated with +// values defined in project properties. For details of which attributes are included +// and how to customise this process see: https://aka.ms/assembly-info-properties + + +// Setting ComVisible to false makes the types in this assembly not visible to COM +// components. If you need to access a type in this assembly from COM, set the ComVisible +// attribute to true on that type. + +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM. + +[assembly: Guid("1594aae8-0e24-442b-9201-430ce9ee4d2e")] + +[assembly: InternalsVisibleTo("Tests.Microsoft.Quantum.Qir.Tools")] + +// This is required to mock internals in tests. +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/src/Qir/Execution/Tools/Driver/IQirDriverGenerator.cs b/src/Qir/Execution/Tools/Driver/IQirDriverGenerator.cs new file mode 100644 index 00000000000..8f29518aca3 --- /dev/null +++ b/src/Qir/Execution/Tools/Driver/IQirDriverGenerator.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.IO; +using System.Threading.Tasks; +using Microsoft.Quantum.Qir.Serialization; + +namespace Microsoft.Quantum.Qir.Tools.Driver +{ + public interface IQirDriverGenerator + { + Task GenerateAsync(EntryPointOperation entryPoint, Stream stream); + + string GetCommandLineArguments(ExecutionInformation executionInformation); + } +} diff --git a/src/Qir/Execution/Tools/Driver/IQirSimulatorInitializer.cs b/src/Qir/Execution/Tools/Driver/IQirSimulatorInitializer.cs new file mode 100644 index 00000000000..f4d02caee24 --- /dev/null +++ b/src/Qir/Execution/Tools/Driver/IQirSimulatorInitializer.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace Microsoft.Quantum.Qir.Tools.Driver +{ + public interface IQirRuntimeInitializer + { + public string Generate(); + + public IEnumerable Headers { get; } + + public IEnumerable LinkLibraries { get; } + } +} diff --git a/src/Qir/Execution/Tools/Driver/QirCppDriver.cs b/src/Qir/Execution/Tools/Driver/QirCppDriver.cs new file mode 100644 index 00000000000..92f6a860a1b --- /dev/null +++ b/src/Qir/Execution/Tools/Driver/QirCppDriver.cs @@ -0,0 +1,578 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 16.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Microsoft.Quantum.Qir.Tools.Driver +{ + using System.IO; + using Microsoft.Quantum.Qir.Serialization; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "16.0.0.0")] + public partial class QirCppDriver : QirCppDriverBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write(@"//---------------------------------------------------------------------------------------------------------------------- +// +// This code was generated by the Microsoft.Quantum.Qir.Tools package. +// The purpose of this source code file is to provide an entry-point for executing a QIR program. +// It handles parsing of command line arguments, and it invokes an entry-point function exposed by the QIR program. +//---------------------------------------------------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include ""CLI11.hpp"" + +#include ""QirRuntime.hpp"" +#include ""QirContext.hpp"" + +"); + foreach (var header in RuntimeInitializer.Headers) { + this.Write("#include \""); + this.Write(this.ToStringHelper.ToStringWithCulture(header)); + this.Write("\"\r\n"); + } + this.Write("\r\nusing namespace Microsoft::Quantum;\r\nusing namespace std;\r\n"); + if (EntryPoint.ContainsArgumentType(DataType.ArrayType)) { + this.Write(@" +// Auxiliary functions for interop with Q# Array type. +struct InteropArray +{ + int64_t Size; + void* Data; + + InteropArray(int64_t size, void* data) : + Size(size), + Data(data){} +}; + +template +unique_ptr CreateInteropArray(vector& v) +{ + unique_ptr array(new InteropArray(v.size(), v.data())); + return array; +} + +template +void TranslateVector(vector& sourceVector, vector& destinationVector, function translationFunction) +{ + destinationVector.resize(sourceVector.size()); + transform(sourceVector.begin(), sourceVector.end(), destinationVector.begin(), translationFunction); +} +"); + } + if (EntryPoint.ContainsArgumentType(DataType.RangeType) || EntryPoint.ContainsArrayType(DataType.RangeType)) { + this.Write(@" +// Auxiliary functions for interop with Q# Range type. +using RangeTuple = tuple; +struct InteropRange +{ + int64_t Start; + int64_t Step; + int64_t End; + + InteropRange() : + Start(0), + Step(0), + End(0){} + + InteropRange(RangeTuple rangeTuple) : + Start(get<0>(rangeTuple)), + Step(get<1>(rangeTuple)), + End(get<2>(rangeTuple)){} +}; + +unique_ptr CreateInteropRange(RangeTuple rangeTuple) +{ + unique_ptr range(new InteropRange(rangeTuple)); + return range; +} + +InteropRange* TranslateRangeTupleToInteropRangePointer(RangeTuple& rangeTuple) +{ + InteropRange* range = new InteropRange(rangeTuple); + return range; +} +"); + } + if (EntryPoint.ContainsArrayType(DataType.RangeType)) { + this.Write("\r\n// Auxiliary functions for interop with Q# Range[] type\r\ntemplate\r\n" + + "void FreePointerVector(vector& v)\r\n{\r\n for (auto p : v)\r\n {\r\n d" + + "elete p;\r\n }\r\n}\r\n"); + } + if (EntryPoint.ContainsArgumentType(DataType.BoolType) || EntryPoint.ContainsArrayType(DataType.BoolType)) { + this.Write("\r\n// Auxiliary functions for interop with Q# Bool type.\r\nconst char InteropFalseA" + + "sChar = 0x0;\r\nconst char InteropTrueAsChar = 0x1;\r\nmap "); + this.Write(this.ToStringHelper.ToStringWithCulture(QirCppInterop.CliOptionTransformerMapName(DataType.BoolType))); + this.Write("{\r\n {\"0\", InteropFalseAsChar},\r\n {\"false\", InteropFalseAsChar},\r\n {\"1\", " + + "InteropTrueAsChar},\r\n {\"true\", InteropTrueAsChar}\r\n};\r\n"); + } + if (EntryPoint.ContainsArgumentType(DataType.PauliType) || EntryPoint.ContainsArrayType(DataType.PauliType)) { + this.Write("\r\n// Auxiliary functions for interop with Q# Pauli type.\r\nmap "); + this.Write(this.ToStringHelper.ToStringWithCulture(QirCppInterop.CliOptionTransformerMapName(DataType.PauliType))); + this.Write("{\r\n {\"PauliI\", PauliId::PauliId_I},\r\n {\"PauliX\", PauliId::PauliId_X},\r\n " + + "{\"PauliY\", PauliId::PauliId_Y},\r\n {\"PauliZ\", PauliId::PauliId_Z}\r\n};\r\n\r\nchar " + + "TranslatePauliToChar(PauliId& pauli)\r\n{\r\n return static_cast(pauli);\r\n}" + + "\r\n"); + } + if (EntryPoint.ContainsArgumentType(DataType.ResultType) || EntryPoint.ContainsArrayType(DataType.ResultType)) { + this.Write("\r\n// Auxiliary functions for interop with Q# Result type.\r\nconst char InteropResu" + + "ltZeroAsChar = 0x0;\r\nconst char InteropResultOneAsChar = 0x1;\r\nmap" + + " "); + this.Write(this.ToStringHelper.ToStringWithCulture(QirCppInterop.CliOptionTransformerMapName(DataType.ResultType))); + this.Write("{\r\n {\"0\", InteropResultZeroAsChar},\r\n {\"Zero\", InteropResultZeroAsChar},\r\n " + + " {\"1\", InteropResultOneAsChar},\r\n {\"One\", InteropResultOneAsChar}\r\n};\r\n"); + } + if (EntryPoint.ContainsArgumentType(DataType.StringType) || EntryPoint.ContainsArrayType(DataType.StringType)) { + this.Write("\r\n// Auxiliary functions for interop with Q# String type.\r\nconst char* TranslateS" + + "tringToCharBuffer(string& s)\r\n{\r\n return s.c_str();\r\n}\r\n"); + } + this.Write("\r\nextern \"C\" void "); + this.Write(this.ToStringHelper.ToStringWithCulture(EntryPoint.Name)); + this.Write("(\r\n"); + for (int i = 0; i < EntryPoint.Parameters.Count; i++) { + var arg = EntryPoint.Parameters[i]; + var isLastArg = i == (EntryPoint.Parameters.Count-1); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.InteropType())); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.Name)); + this.Write(this.ToStringHelper.ToStringWithCulture((isLastArg) ? "" : ",")); + this.Write("\r\n"); + } + this.Write("); // QIR interop function.\r\n\r\nint main(int argc, char* argv[])\r\n{\r\n CLI::App " + + "app(\"QIR Standalone Entry Point\");\r\n\r\n // Initialize runtime.\r\n"); + var initializerReader = new StringReader(RuntimeInitializer.Generate()); + string line; + while((line = initializerReader.ReadLine()) != null) { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(line)); + this.Write("\r\n"); + } + this.Write(@" + // Add the --simulation-output option. + string simulationOutputFile; + CLI::Option* simulationOutputFileOpt = app.add_option( + ""--simulation-output"", + simulationOutputFile, + ""File where the output produced during the simulation is written""); + +"); + if (EntryPoint.Parameters.Count > 0) { + this.Write(" // Add a command line option for each entry-point parameter.\r\n"); + } + foreach (var arg in EntryPoint.Parameters) { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.CliOptionType())); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.CliOptionVariableName())); + this.Write(";\r\n"); + if (arg.CliOptionVariableDefaultValue() != null) { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.CliOptionVariableName())); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.CliOptionVariableDefaultValue())); + this.Write(";\r\n"); + } + this.Write(" app.add_option(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.CliOptionName())); + this.Write("\", "); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.CliOptionVariableName())); + this.Write(", \""); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.CliOptionDescription())); + this.Write("\")\r\n ->required()"); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.CliOptionTransformerMapName() != null ? "" : ";")); + this.Write("\r\n"); + if (arg.CliOptionTransformerMapName() != null) { + this.Write(" ->transform(CLI::CheckedTransformer("); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.CliOptionTransformerMapName())); + this.Write(", CLI::ignore_case));\r\n"); + } + this.Write("\r\n"); + } + this.Write(" // After all the options have been added, parse arguments from the command li" + + "ne.\r\n CLI11_PARSE(app, argc, argv);\r\n\r\n"); + if (EntryPoint.Parameters.Count > 0) { + this.Write(" // Cast parsed arguments to its interop types.\r\n"); + } + foreach (var arg in EntryPoint.Parameters) { + if (arg.Type == DataType.ArrayType) { + var arrayInteropTranslator = QirCppInterop.CliOptionTypeToInteropTypeTranslator(arg.ArrayType); + if (arrayInteropTranslator == null) { + this.Write(" unique_ptr "); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.UniquePtrVariableName())); + this.Write(" = CreateInteropArray("); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.CliOptionVariableName())); + this.Write(");\r\n"); + } + else { + var arrayCliOptionType = QirCppInterop.CliOptionType(arg.ArrayType); + var arrayInteropType = QirCppInterop.InteropType(arg.ArrayType); + this.Write(" vector<"); + this.Write(this.ToStringHelper.ToStringWithCulture(arrayInteropType)); + this.Write("> "); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.IntermediateVariableName())); + this.Write(";\r\n TranslateVector<"); + this.Write(this.ToStringHelper.ToStringWithCulture(arrayCliOptionType)); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(arrayInteropType)); + this.Write(">("); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.CliOptionVariableName())); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.IntermediateVariableName())); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(arrayInteropTranslator)); + this.Write(");\r\n unique_ptr "); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.UniquePtrVariableName())); + this.Write(" = CreateInteropArray("); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.IntermediateVariableName())); + this.Write(");\r\n"); + } + this.Write(" InteropArray* "); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.InteropVariableName())); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.UniquePtrVariableName())); + this.Write(".get();\r\n"); + } + else { + var interopTranslator = QirCppInterop.CliOptionTypeToInteropTypeTranslator(arg.Type); + if (interopTranslator == null) { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.InteropType())); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.InteropVariableName())); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.CliOptionVariableName())); + this.Write(";\r\n"); + } + else { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.InteropType())); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.InteropVariableName())); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(interopTranslator)); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.CliOptionVariableName())); + this.Write(");\r\n"); + } + } + this.Write("\r\n"); + } + this.Write(@" // Redirect the simulator output from std::cout if the --simulation-output option is present. + ostream* simulatorOutputStream = &cout; + ofstream simulationOutputFileStream; + if (!simulationOutputFileOpt->empty()) + { + simulationOutputFileStream.open(simulationOutputFile); + SetOutputStream(simulationOutputFileStream); + simulatorOutputStream = &simulationOutputFileStream; + } + + // Execute the entry point operation. + "); + this.Write(this.ToStringHelper.ToStringWithCulture(EntryPoint.Name)); + this.Write("(\r\n"); + for (int i = 0; i < EntryPoint.Parameters.Count; i++) { + var arg = EntryPoint.Parameters[i]; + var isLastArg = i == (EntryPoint.Parameters.Count-1); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(arg.InteropVariableName())); + this.Write(this.ToStringHelper.ToStringWithCulture((isLastArg) ? "" : ",")); + this.Write("\r\n"); + } + this.Write(" );\r\n\r\n // Flush the output of the simulation.\r\n simulatorOutputStream->" + + "flush();\r\n if (simulationOutputFileStream.is_open())\r\n {\r\n simulati" + + "onOutputFileStream.close();\r\n }\r\n\r\n return 0;\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "16.0.0.0")] + public class QirCppDriverBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + protected System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/src/Qir/Execution/Tools/Driver/QirCppDriver.tt b/src/Qir/Execution/Tools/Driver/QirCppDriver.tt new file mode 100644 index 00000000000..76e7d9312a6 --- /dev/null +++ b/src/Qir/Execution/Tools/Driver/QirCppDriver.tt @@ -0,0 +1,248 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.IO" #> +<#@ import namespace="Microsoft.Quantum.Qir.Serialization" #> +//---------------------------------------------------------------------------------------------------------------------- +// +// This code was generated by the Microsoft.Quantum.Qir.Tools package. +// The purpose of this source code file is to provide an entry-point for executing a QIR program. +// It handles parsing of command line arguments, and it invokes an entry-point function exposed by the QIR program. +//---------------------------------------------------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "CLI11.hpp" + +#include "QirRuntime.hpp" +#include "QirContext.hpp" + +<# foreach (var header in RuntimeInitializer.Headers) { #> +#include "<#= header #>" +<# } #> + +using namespace Microsoft::Quantum; +using namespace std; +<# if (EntryPoint.ContainsArgumentType(DataType.ArrayType)) { #> + +// Auxiliary functions for interop with Q# Array type. +struct InteropArray +{ + int64_t Size; + void* Data; + + InteropArray(int64_t size, void* data) : + Size(size), + Data(data){} +}; + +template +unique_ptr CreateInteropArray(vector& v) +{ + unique_ptr array(new InteropArray(v.size(), v.data())); + return array; +} + +template +void TranslateVector(vector& sourceVector, vector& destinationVector, function translationFunction) +{ + destinationVector.resize(sourceVector.size()); + transform(sourceVector.begin(), sourceVector.end(), destinationVector.begin(), translationFunction); +} +<# } #> +<# if (EntryPoint.ContainsArgumentType(DataType.RangeType) || EntryPoint.ContainsArrayType(DataType.RangeType)) { #> + +// Auxiliary functions for interop with Q# Range type. +using RangeTuple = tuple; +struct InteropRange +{ + int64_t Start; + int64_t Step; + int64_t End; + + InteropRange() : + Start(0), + Step(0), + End(0){} + + InteropRange(RangeTuple rangeTuple) : + Start(get<0>(rangeTuple)), + Step(get<1>(rangeTuple)), + End(get<2>(rangeTuple)){} +}; + +unique_ptr CreateInteropRange(RangeTuple rangeTuple) +{ + unique_ptr range(new InteropRange(rangeTuple)); + return range; +} + +InteropRange* TranslateRangeTupleToInteropRangePointer(RangeTuple& rangeTuple) +{ + InteropRange* range = new InteropRange(rangeTuple); + return range; +} +<# } #> +<# if (EntryPoint.ContainsArrayType(DataType.RangeType)) { #> + +// Auxiliary functions for interop with Q# Range[] type +template +void FreePointerVector(vector& v) +{ + for (auto p : v) + { + delete p; + } +} +<# } #> +<# if (EntryPoint.ContainsArgumentType(DataType.BoolType) || EntryPoint.ContainsArrayType(DataType.BoolType)) { #> + +// Auxiliary functions for interop with Q# Bool type. +const char InteropFalseAsChar = 0x0; +const char InteropTrueAsChar = 0x1; +map <#= QirCppInterop.CliOptionTransformerMapName(DataType.BoolType) #>{ + {"0", InteropFalseAsChar}, + {"false", InteropFalseAsChar}, + {"1", InteropTrueAsChar}, + {"true", InteropTrueAsChar} +}; +<# } #> +<# if (EntryPoint.ContainsArgumentType(DataType.PauliType) || EntryPoint.ContainsArrayType(DataType.PauliType)) { #> + +// Auxiliary functions for interop with Q# Pauli type. +map <#= QirCppInterop.CliOptionTransformerMapName(DataType.PauliType) #>{ + {"PauliI", PauliId::PauliId_I}, + {"PauliX", PauliId::PauliId_X}, + {"PauliY", PauliId::PauliId_Y}, + {"PauliZ", PauliId::PauliId_Z} +}; + +char TranslatePauliToChar(PauliId& pauli) +{ + return static_cast(pauli); +} +<# } #> +<# if (EntryPoint.ContainsArgumentType(DataType.ResultType) || EntryPoint.ContainsArrayType(DataType.ResultType)) { #> + +// Auxiliary functions for interop with Q# Result type. +const char InteropResultZeroAsChar = 0x0; +const char InteropResultOneAsChar = 0x1; +map <#= QirCppInterop.CliOptionTransformerMapName(DataType.ResultType) #>{ + {"0", InteropResultZeroAsChar}, + {"Zero", InteropResultZeroAsChar}, + {"1", InteropResultOneAsChar}, + {"One", InteropResultOneAsChar} +}; +<# } #> +<# if (EntryPoint.ContainsArgumentType(DataType.StringType) || EntryPoint.ContainsArrayType(DataType.StringType)) { #> + +// Auxiliary functions for interop with Q# String type. +const char* TranslateStringToCharBuffer(string& s) +{ + return s.c_str(); +} +<# } #> + +extern "C" void <#= EntryPoint.Name #>( +<# for (int i = 0; i < EntryPoint.Parameters.Count; i++) { + var arg = EntryPoint.Parameters[i]; + var isLastArg = i == (EntryPoint.Parameters.Count-1); #> + <#= arg.InteropType() #> <#= arg.Name #><#= (isLastArg) ? "" : "," #> +<# } #> +); // QIR interop function. + +int main(int argc, char* argv[]) +{ + CLI::App app("QIR Standalone Entry Point"); + + // Initialize runtime. +<# var initializerReader = new StringReader(RuntimeInitializer.Generate()); + string line; + while((line = initializerReader.ReadLine()) != null) { #> + <#= line #> +<# } #> + + // Add the --simulation-output option. + string simulationOutputFile; + CLI::Option* simulationOutputFileOpt = app.add_option( + "--simulation-output", + simulationOutputFile, + "File where the output produced during the simulation is written"); + +<# if (EntryPoint.Parameters.Count > 0) { #> + // Add a command line option for each entry-point parameter. +<# } #> +<# foreach (var arg in EntryPoint.Parameters) { #> + <#= arg.CliOptionType() #> <#= arg.CliOptionVariableName() #>; +<# if (arg.CliOptionVariableDefaultValue() != null) { #> + <#= arg.CliOptionVariableName() #> = <#= arg.CliOptionVariableDefaultValue() #>; +<# } #> + app.add_option("<#= arg.CliOptionName() #>", <#= arg.CliOptionVariableName() #>, "<#= arg.CliOptionDescription() #>") + ->required()<#= arg.CliOptionTransformerMapName() != null ? "" : ";" #> +<# if (arg.CliOptionTransformerMapName() != null) { #> + ->transform(CLI::CheckedTransformer(<#= arg.CliOptionTransformerMapName() #>, CLI::ignore_case)); +<# } #> + +<# } #> + // After all the options have been added, parse arguments from the command line. + CLI11_PARSE(app, argc, argv); + +<# if (EntryPoint.Parameters.Count > 0) { #> + // Cast parsed arguments to its interop types. +<# } #> +<# foreach (var arg in EntryPoint.Parameters) { #> +<# if (arg.Type == DataType.ArrayType) { + var arrayInteropTranslator = QirCppInterop.CliOptionTypeToInteropTypeTranslator(arg.ArrayType); #> +<# if (arrayInteropTranslator == null) { #> + unique_ptr <#= arg.UniquePtrVariableName() #> = CreateInteropArray(<#= arg.CliOptionVariableName() #>); +<# } #> +<# else { + var arrayCliOptionType = QirCppInterop.CliOptionType(arg.ArrayType); + var arrayInteropType = QirCppInterop.InteropType(arg.ArrayType); #> + vector<<#= arrayInteropType #>> <#= arg.IntermediateVariableName() #>; + TranslateVector<<#= arrayCliOptionType #>, <#= arrayInteropType #>>(<#= arg.CliOptionVariableName() #>, <#= arg.IntermediateVariableName() #>, <#= arrayInteropTranslator #>); + unique_ptr <#= arg.UniquePtrVariableName() #> = CreateInteropArray(<#= arg.IntermediateVariableName() #>); +<# } #> + InteropArray* <#= arg.InteropVariableName() #> = <#= arg.UniquePtrVariableName() #>.get(); +<# } #> +<# else { + var interopTranslator = QirCppInterop.CliOptionTypeToInteropTypeTranslator(arg.Type); #> +<# if (interopTranslator == null) { #> + <#= arg.InteropType() #> <#= arg.InteropVariableName() #> = <#= arg.CliOptionVariableName() #>; +<# } #> +<# else { #> + <#= arg.InteropType() #> <#= arg.InteropVariableName() #> = <#= interopTranslator #>(<#= arg.CliOptionVariableName() #>); +<# } #> +<# } #> + +<# } #> + // Redirect the simulator output from std::cout if the --simulation-output option is present. + ostream* simulatorOutputStream = &cout; + ofstream simulationOutputFileStream; + if (!simulationOutputFileOpt->empty()) + { + simulationOutputFileStream.open(simulationOutputFile); + SetOutputStream(simulationOutputFileStream); + simulatorOutputStream = &simulationOutputFileStream; + } + + // Execute the entry point operation. + <#= EntryPoint.Name #>( +<# for (int i = 0; i < EntryPoint.Parameters.Count; i++) { + var arg = EntryPoint.Parameters[i]; + var isLastArg = i == (EntryPoint.Parameters.Count-1); #> + <#= arg.InteropVariableName() #><#= (isLastArg) ? "" : "," #> +<# } #> + ); + + // Flush the output of the simulation. + simulatorOutputStream->flush(); + if (simulationOutputFileStream.is_open()) + { + simulationOutputFileStream.close(); + } + + return 0; +} diff --git a/src/Qir/Execution/Tools/Driver/QirCppDriverEx.cs b/src/Qir/Execution/Tools/Driver/QirCppDriverEx.cs new file mode 100644 index 00000000000..af2b2ce0b02 --- /dev/null +++ b/src/Qir/Execution/Tools/Driver/QirCppDriverEx.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Quantum.Qir.Serialization; + +namespace Microsoft.Quantum.Qir.Tools.Driver +{ + public partial class QirCppDriver + { + public readonly EntryPointOperation EntryPoint; + + public readonly IQirRuntimeInitializer RuntimeInitializer; + + public QirCppDriver(EntryPointOperation entryPoint, IQirRuntimeInitializer runtimeInitializer) + { + EntryPoint = entryPoint; + RuntimeInitializer = runtimeInitializer; + } + } +} diff --git a/src/Qir/Execution/Tools/Driver/QirCppDriverGenerator.cs b/src/Qir/Execution/Tools/Driver/QirCppDriverGenerator.cs new file mode 100644 index 00000000000..11b8caccce5 --- /dev/null +++ b/src/Qir/Execution/Tools/Driver/QirCppDriverGenerator.cs @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Quantum.Qir.Serialization; + +namespace Microsoft.Quantum.Qir.Tools.Driver +{ + public class QirCppDriverGenerator : IQirDriverGenerator + { + private readonly IQirRuntimeInitializer RuntimeInitalizer; + + private delegate string StringConversion(ArgumentValue value); + + public QirCppDriverGenerator(IQirRuntimeInitializer runtimeInitializer) + { + RuntimeInitalizer = runtimeInitializer; + } + + public async Task GenerateAsync(EntryPointOperation entryPointOperation, Stream stream) + { + var qirCppDriver = new QirCppDriver(entryPointOperation, RuntimeInitalizer); + var cppSource = qirCppDriver.TransformText(); + await stream.WriteAsync(Encoding.UTF8.GetBytes(cppSource)); + await stream.FlushAsync(); + stream.Position = 0; + } + + public string GetCommandLineArguments(ExecutionInformation executionInformation) + { + // Sort arguments by position. + var sortedArguments = executionInformation.EntryPoint.Parameters.OrderBy(arg => arg.Position); + var argumentBuilder = new StringBuilder(); + foreach (var arg in sortedArguments) + { + if (argumentBuilder.Length != 0) + { + argumentBuilder.Append(' '); + } + + argumentBuilder.Append($"--{arg.Name}").Append(' ').Append(GetArgumentValueString(arg, executionInformation.ArgumentValues[arg.Name])); + } + + return argumentBuilder.ToString(); + } + + private static string GetArgumentValueString(Parameter argument, ArgumentValue argumentValue) + { + // Today, only the first argument value in the array will be used. + return argument.Type switch + { + DataType.BoolType => GetBoolValueString(argumentValue), + DataType.DoubleType => GetDoubleValueString(argumentValue), + DataType.IntegerType => GetIntegerValueString(argumentValue), + DataType.PauliType => GetPauliValueString(argumentValue), + DataType.RangeType => GetRangeValueString(argumentValue), + DataType.ResultType => GetResultValueString(argumentValue), + DataType.StringType => GetStringValueString(argumentValue), + DataType.ArrayType => GetArrayValueString(argument.ArrayType, argumentValue.Array), + _ => throw new ArgumentException($"Unsupported data type {argument.Type}") + }; + } + + private static string GetArrayValueString(DataType? arrayType, IList arrayValue) + { + static string ConvertArray(IList list, StringConversion conversion) => + string.Join(' ', list.Select(item => conversion.Invoke(item))); + + return arrayType switch + { + DataType.BoolType => ConvertArray(arrayValue, GetBoolValueString), + DataType.DoubleType => ConvertArray(arrayValue, GetDoubleValueString), + DataType.IntegerType => ConvertArray(arrayValue, GetIntegerValueString), + DataType.PauliType => ConvertArray(arrayValue, GetPauliValueString), + DataType.RangeType => ConvertArray(arrayValue, GetRangeValueString), + DataType.ResultType => ConvertArray(arrayValue, GetResultValueString), + DataType.StringType => ConvertArray(arrayValue, GetStringValueString), + _ => throw new ArgumentException($"Unsupported array data type {arrayType}") + }; + } + + private static string GetResultValueString(ArgumentValue value) + { + if (value?.Result == null) + { + throw new ArgumentNullException("Cannot convert null result value to string."); + } + return value.Result == ResultValue.One ? "1" : "0"; + } + + private static string GetRangeValueString(ArgumentValue value) + { + if (value?.Range == null) + { + throw new ArgumentNullException("Cannot convert null range value to string."); + } + var rangeValue = value.Range; + return $"{rangeValue.Start} {rangeValue.Step} {rangeValue.End}"; + } + + private static string GetStringValueString(ArgumentValue value) + { + if (value?.String == null) + { + throw new ArgumentNullException("Cannot convert null string value to string."); + } + + return $"\"{value.String}\""; + } + + private static string GetBoolValueString(ArgumentValue value) + { + if (value?.Bool == null) + { + throw new ArgumentNullException("Cannot convert null bool value to string."); + } + + return value.Bool.ToString().ToLower(); + } + + private static string GetPauliValueString(ArgumentValue value) + { + if (value?.Pauli == null) + { + throw new ArgumentNullException("Cannot convert null pauli value to string."); + } + + return value.Pauli.ToString().ToLower(); + } + + private static string GetDoubleValueString(ArgumentValue value) + { + if (value?.Double == null) + { + throw new ArgumentNullException("Cannot convert null double value to string."); + } + + return value.Double.ToString().ToLower(); + } + + private static string GetIntegerValueString(ArgumentValue value) + { + if (value?.Integer == null) + { + throw new ArgumentNullException("Cannot convert null integer value to string."); + } + + return value.Integer.ToString().ToLower(); + } + } +} diff --git a/src/Qir/Execution/Tools/Driver/QirCppFullStateSimulatorInitializer.cs b/src/Qir/Execution/Tools/Driver/QirCppFullStateSimulatorInitializer.cs new file mode 100644 index 00000000000..44939338006 --- /dev/null +++ b/src/Qir/Execution/Tools/Driver/QirCppFullStateSimulatorInitializer.cs @@ -0,0 +1,302 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 16.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Microsoft.Quantum.Qir.Tools.Driver +{ + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "16.0.0.0")] + public partial class QirCppFullStateSimulatorInitializer : QirCppFullStateSimulatorInitializerBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("unique_ptr sim = CreateFullstateSimulator();\r\nQirContextScope qir" + + "ctx(sim.get(), false /*trackAllocatedObjects*/);"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "16.0.0.0")] + public class QirCppFullStateSimulatorInitializerBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + protected System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/src/Qir/Execution/Tools/Driver/QirCppFullStateSimulatorInitializer.tt b/src/Qir/Execution/Tools/Driver/QirCppFullStateSimulatorInitializer.tt new file mode 100644 index 00000000000..275994cf4af --- /dev/null +++ b/src/Qir/Execution/Tools/Driver/QirCppFullStateSimulatorInitializer.tt @@ -0,0 +1,3 @@ +<#@ template language="C#" linePragmas="false" #> +unique_ptr sim = CreateFullstateSimulator(); +QirContextScope qirctx(sim.get(), false /*trackAllocatedObjects*/); \ No newline at end of file diff --git a/src/Qir/Execution/Tools/Driver/QirCppInterop.cs b/src/Qir/Execution/Tools/Driver/QirCppInterop.cs new file mode 100644 index 00000000000..dfac2fac833 --- /dev/null +++ b/src/Qir/Execution/Tools/Driver/QirCppInterop.cs @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Quantum.Qir.Serialization; + +#nullable enable + +namespace Microsoft.Quantum.Qir.Tools.Driver +{ + internal static class QirCppInterop + { + public static string? CliOptionTransformerMapName(DataType? dataType) => + dataType switch + { + DataType.BoolType => "BoolAsCharMap", + DataType.IntegerType => null, + DataType.DoubleType => null, + DataType.PauliType => "PauliMap", + DataType.RangeType => null, + DataType.ResultType => "ResultAsCharMap", + DataType.StringType => null, + DataType.ArrayType => null, + _ => throw new ArgumentException($"Invalid data type: {dataType}") + }; + + public static string CliOptionType(DataType? dataType) => + dataType switch + { + DataType.BoolType => "char", + DataType.IntegerType => "int64_t", + DataType.DoubleType => "double_t", + DataType.PauliType => "PauliId", + DataType.RangeType => "RangeTuple", + DataType.ResultType => "char", + DataType.StringType => "string", + DataType.ArrayType => throw new NotSupportedException($"{DataType.ArrayType} does not match to a specific CLI option variable type"), + _ => throw new ArgumentException($"Invalid data type: {dataType}") + }; + + public static string? CliOptionTypeToInteropTypeTranslator(DataType? dataType) => + dataType switch + { + DataType.BoolType => null, + DataType.IntegerType => null, + DataType.DoubleType => null, + DataType.PauliType => "TranslatePauliToChar", + DataType.RangeType => "TranslateRangeTupleToInteropRangePointer", + DataType.ResultType => null, + DataType.StringType => "TranslateStringToCharBuffer", + DataType.ArrayType => throw new NotSupportedException($"{DataType.ArrayType} does not match to a specific CLI option variable type"), + _ => throw new ArgumentException($"Invalid data type: {dataType}") + }; + + public static string? CliOptionVariableDefaultValue(DataType? dataType) => + dataType switch + { + DataType.BoolType => "InteropFalseAsChar", + DataType.IntegerType => "0", + DataType.DoubleType => "0.0", + DataType.PauliType => "PauliId::PauliId_I", + DataType.RangeType => null, + DataType.ResultType => "InteropResultZeroAsChar", + DataType.StringType => null, + DataType.ArrayType => null, + _ => throw new ArgumentException($"Invalid data type: {dataType}") + }; + + public static string InteropType(DataType? dataType) => + dataType switch + { + DataType.BoolType => "char", + DataType.IntegerType => "int64_t", + DataType.DoubleType => "double_t", + DataType.PauliType => "char", + DataType.RangeType => "InteropRange*", + DataType.ResultType => "char", + DataType.StringType => "const char*", + DataType.ArrayType => "InteropArray*", + _ => throw new ArgumentException($"Invalid data type: {dataType}") + }; + } + + internal static class ArgumentCppExtensions + { + public static string CliOptionDescription(this Parameter @this) => + $"Option to provide a value for the {@this.Name} parameter"; + + public static string CliOptionName(this Parameter @this) + { + if (String.IsNullOrEmpty(@this.Name)) + { + throw new InvalidOperationException($"Invalid parameter name '{@this.Name}'"); + } + + return @this.Name.Length == 1 ? $"-{@this.Name}" : $"--{@this.Name}"; + } + + public static string? CliOptionTransformerMapName(this Parameter @this) => + @this.Type switch + { + DataType.ArrayType => QirCppInterop.CliOptionTransformerMapName(@this.ArrayType), + _ => QirCppInterop.CliOptionTransformerMapName(@this.Type) + }; + + public static string CliOptionType(this Parameter @this) => + @this.Type switch + { + DataType.ArrayType => $"vector<{QirCppInterop.CliOptionType(@this.ArrayType)}>", + _ => QirCppInterop.CliOptionType(@this.Type) + }; + + public static string CliOptionVariableName(this Parameter @this) => $"{@this.Name}Cli"; + + public static string? CliOptionVariableDefaultValue(this Parameter @this) => + QirCppInterop.CliOptionVariableDefaultValue(@this.Type); + + public static string IntermediateVariableName(this Parameter @this) => $"{@this.Name}Intermediate"; + + public static string InteropVariableName(this Parameter @this) => $"{@this.Name}Interop"; + + public static string InteropType(this Parameter @this) => QirCppInterop.InteropType(@this.Type); + + public static string UniquePtrVariableName(this Parameter @this) => $"{@this.Name}UniquePtr"; + } + + internal static class EntryPointOperationCppExtension + { + public static bool ContainsArgumentType(this EntryPointOperation @this, DataType type) => + @this.Parameters.Where(arg => arg.Type == type).Any(); + + public static bool ContainsArrayType(this EntryPointOperation @this, DataType type) => + @this.Parameters.Where(arg => arg.ArrayType == type).Any(); + } +} diff --git a/src/Qir/Execution/Tools/Driver/QirFullStateDriverGenerator.cs b/src/Qir/Execution/Tools/Driver/QirFullStateDriverGenerator.cs new file mode 100644 index 00000000000..381a06b532f --- /dev/null +++ b/src/Qir/Execution/Tools/Driver/QirFullStateDriverGenerator.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.IO; +using System.Threading.Tasks; +using Microsoft.Quantum.Qir.Serialization; + +namespace Microsoft.Quantum.Qir.Tools.Driver +{ + public class QirFullStateDriverGenerator: IQirDriverGenerator + { + private readonly QirCppDriverGenerator DriverGenerator; + public QirFullStateDriverGenerator() + { + DriverGenerator = new QirCppDriverGenerator(new QirFullStateSimulatorInitializer()); + } + + public async Task GenerateAsync(EntryPointOperation entryPoint, Stream stream) => + await DriverGenerator.GenerateAsync(entryPoint, stream); + + public string GetCommandLineArguments(ExecutionInformation executionInformation) => + DriverGenerator.GetCommandLineArguments(executionInformation); + } +} diff --git a/src/Qir/Execution/Tools/Driver/QirFullStateSimulatorInitializer.cs b/src/Qir/Execution/Tools/Driver/QirFullStateSimulatorInitializer.cs new file mode 100644 index 00000000000..190dc93cf07 --- /dev/null +++ b/src/Qir/Execution/Tools/Driver/QirFullStateSimulatorInitializer.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Quantum.Qir.Tools.Driver +{ + public class QirFullStateSimulatorInitializer : IQirRuntimeInitializer + { + public string Generate() => new QirCppFullStateSimulatorInitializer().TransformText(); + + public IEnumerable Headers => new [] { + "SimFactory.hpp" + }; + + public IEnumerable LinkLibraries => new string[0]; + } +} diff --git a/src/Qir/Execution/Tools/EntryPointOperationLoader.cs b/src/Qir/Execution/Tools/EntryPointOperationLoader.cs new file mode 100644 index 00000000000..7713ff0d03f --- /dev/null +++ b/src/Qir/Execution/Tools/EntryPointOperationLoader.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.Quantum.Qir.Serialization; +using Microsoft.Quantum.QsCompiler; +using Microsoft.Quantum.QsCompiler.SyntaxTokens; +using Microsoft.Quantum.QsCompiler.SyntaxTree; +using Microsoft.Quantum.QsCompiler.QIR; + +namespace Microsoft.Quantum.Qir.Tools +{ + using QsTypeKind = QsTypeKind; + + internal static class EntryPointLoader + { + /// + /// Loads the entry point operations found in the syntax tree included as a resource from . + /// + /// The file info of a .NET DLL from which to load entry point operations from. + /// + /// A list of entry point operation objects representing the QIR entry point operations. + /// + /// does not exist. + /// does not contain a Q# syntax tree. + /// Encounters invalid parameters for an entry point. + public static IList LoadEntryPointOperations(FileInfo assemblyFileInfo) + { + if (!AssemblyLoader.LoadReferencedAssembly(assemblyFileInfo.FullName, out var compilation)) + { + throw new ArgumentException("Unable to read the Q# syntax tree from the given DLL."); + } + + return GenerateEntryPointOperations(compilation); + } + + private static IList GenerateEntryPointOperations(QsCompilation compilation) + { + var globals = compilation.Namespaces.GlobalCallableResolutions(); + + return compilation.EntryPoints.Select(ep => new EntryPointOperation() + { + Name = NameGeneration.InteropFriendlyWrapperName(ep), + Parameters = GetParams(globals[ep]), + }).ToList(); + } + + private static List GetParams(QsCallable callable) + { + return SyntaxGenerator.ExtractItems(callable.ArgumentTuple) + .Where(sym => !sym.Type.Resolution.IsUnitType) + .Select(sym => MakeParameter(sym)) + .ToList(); + } + + private static Parameter MakeParameter(LocalVariableDeclaration parameter) + { + var type = MapResolvedTypeToDataType(parameter.Type); + var arrayType = parameter.Type.Resolution is QsTypeKind.ArrayType innerType + ? (DataType?)MapResolvedTypeToDataType(innerType.Item) + : null; + + if (arrayType == DataType.ArrayType) + { + throw new ArgumentException("Multi-dimensional arrays are not supported types of entry point parameters."); + } + + return new Parameter() + { + Name = parameter.VariableName is QsLocalSymbol.ValidName name + ? name.Item + : throw new ArgumentException("Encountered invalid name for parameter."), + Type = type, + ArrayType = arrayType, + }; + } + + private static DataType MapResolvedTypeToDataType(ResolvedType rt) => rt.Resolution.Tag switch + { + QsTypeKind.Tags.Bool => DataType.BoolType, + QsTypeKind.Tags.Int => DataType.IntegerType, + QsTypeKind.Tags.Double => DataType.DoubleType, + QsTypeKind.Tags.Pauli => DataType.PauliType, + QsTypeKind.Tags.Range => DataType.RangeType, + QsTypeKind.Tags.Result => DataType.ResultType, + QsTypeKind.Tags.String => DataType.StringType, + QsTypeKind.Tags.ArrayType => DataType.ArrayType, + _ => throw new ArgumentException($"Invalid type ({rt.Resolution.Tag}) for entry point parameter"), + }; + } +} diff --git a/src/Qir/Execution/Tools/Executable/ClangClient.cs b/src/Qir/Execution/Tools/Executable/ClangClient.cs new file mode 100644 index 00000000000..4f4fca743b8 --- /dev/null +++ b/src/Qir/Execution/Tools/Executable/ClangClient.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +#nullable enable + +namespace Microsoft.Quantum.Qir.Tools.Executable +{ + internal class ClangClient : IClangClient + { + private const string LinkFlag = " -l "; + private const string LibraryPathFlag = " -L "; + private const string IncludePathFlag = " -I "; + private readonly ILogger? logger; + + public ClangClient(ILogger? logger) + { + this.logger = logger; + } + + public async Task CreateExecutableAsync(string[] inputFiles, string[] linkedLibraries, string[] libraryPaths, string[] includePaths, string outputPath) + { + var inputsArg = string.Join(' ', inputFiles); + + // string.Join does not automatically prepend the delimiter, so it is included again in the string here. + var linkedLibrariesArg = $"{LinkFlag} {string.Join(LinkFlag, linkedLibraries)}"; + var libraryPathsArg = $"{LibraryPathFlag} {string.Join(LibraryPathFlag, libraryPaths)}"; + var includePathsArg = $"{IncludePathFlag} {string.Join(IncludePathFlag, includePaths)}"; + var arguments = $"{inputsArg} {includePathsArg} {libraryPathsArg} {linkedLibrariesArg} -o {outputPath} -std=c++17 -v"; + logger?.LogInformation($"Invoking clang with the following arguments: {arguments}"); + var taskCompletionSource = new TaskCompletionSource(); + using var process = new Process(); + AddPathsToEnvironmentVariable("DYLD_LIBRARY_PATH", libraryPaths); + AddPathsToEnvironmentVariable("LD_LIBRARY_PATH", libraryPaths); + process.StartInfo = new ProcessStartInfo + { + FileName = "clang++", + Arguments = arguments, + }; + process.EnableRaisingEvents = true; + process.Exited += (sender, args) => { taskCompletionSource.SetResult(true); }; + process.Start(); + await taskCompletionSource.Task; + } + + private static void AddPathsToEnvironmentVariable(string variable, string[] values) + { + var newValue = string.Join(Path.PathSeparator, values); + var oldValue = Environment.GetEnvironmentVariable(variable); + if (oldValue != null) + { + newValue = oldValue + $"{Path.PathSeparator}{newValue}"; + } + Environment.SetEnvironmentVariable(variable, newValue); + } + } +} diff --git a/src/Qir/Execution/Tools/Executable/IClangClient.cs b/src/Qir/Execution/Tools/Executable/IClangClient.cs new file mode 100644 index 00000000000..87423492187 --- /dev/null +++ b/src/Qir/Execution/Tools/Executable/IClangClient.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Threading.Tasks; + +namespace Microsoft.Quantum.Qir.Tools.Executable +{ + /// + /// Wraps the 'clang' tool used for compilation. + /// + internal interface IClangClient + { + Task CreateExecutableAsync(string[] inputFiles, string[] linkedLibraries, string[] libraryPaths, string[] includePaths, string outputPath); + } +} diff --git a/src/Qir/Execution/Tools/Executable/IQirExecutable.cs b/src/Qir/Execution/Tools/Executable/IQirExecutable.cs new file mode 100644 index 00000000000..e3ad50a6fcb --- /dev/null +++ b/src/Qir/Execution/Tools/Executable/IQirExecutable.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Quantum.Qir.Serialization; + +namespace Microsoft.Quantum.Qir.Tools.Executable +{ + public interface IQirExecutable + { + /// + /// Builds the executable. + /// + /// Entry point operation. + /// Directories containing libraries to link. + /// Directories containing files to include. + /// + Task BuildAsync(EntryPointOperation entryPoint, IList libraryDirectories, IList includeDirectories); + + /// + /// Runs the executable. + /// + /// Execution information to run the executable with.. + /// Stream to which output will be written. + /// + Task RunAsync(ExecutionInformation executionInformation, Stream output); + } +} diff --git a/src/Qir/Execution/Tools/Executable/IQirExecutableGenerator.cs b/src/Qir/Execution/Tools/Executable/IQirExecutableGenerator.cs new file mode 100644 index 00000000000..3715a8fed2e --- /dev/null +++ b/src/Qir/Execution/Tools/Executable/IQirExecutableGenerator.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace Microsoft.Quantum.Qir.Tools.Executable +{ + internal interface IQirExecutableGenerator + { + /// + /// Generates a quantum simulation program executable. + /// + /// File path to create the executable at. Dependencies will be copied to its directory. + /// Location of the source files. + /// Locations of the libraries that must be linked. + /// Locations of the headers that must be included. + /// Libraries to link. + /// + public Task GenerateExecutableAsync(FileInfo executableFile, DirectoryInfo sourceDirectory, IList libraryDirectories, IList includeDirectories, IList linkLibraries); + } +} diff --git a/src/Qir/Execution/Tools/Executable/IQirExecutableRunner.cs b/src/Qir/Execution/Tools/Executable/IQirExecutableRunner.cs new file mode 100644 index 00000000000..7f2b4766ab2 --- /dev/null +++ b/src/Qir/Execution/Tools/Executable/IQirExecutableRunner.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.IO; +using System.Threading.Tasks; + +namespace Microsoft.Quantum.Qir.Tools.Executable +{ + internal interface IQirExecutableRunner + { + /// + /// Runs a QIR executable with the given arguments. + /// + /// Location of the executable to run. + /// Stream to write program output. + /// Arguments to supply the program with. + /// + Task RunExecutableAsync(FileInfo executableFile, Stream stream, string arguments); + } +} diff --git a/src/Qir/Execution/Tools/Executable/QirExecutable.cs b/src/Qir/Execution/Tools/Executable/QirExecutable.cs new file mode 100644 index 00000000000..9400c58b912 --- /dev/null +++ b/src/Qir/Execution/Tools/Executable/QirExecutable.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Quantum.Qir.Serialization; +using Microsoft.Quantum.Qir.Tools.Driver; + +#nullable enable + +namespace Microsoft.Quantum.Qir.Tools.Executable +{ + /// + /// Base for creating and running QIR-based executables. + /// + public abstract class QirExecutable : IQirExecutable + { + private const string DriverFileName = "driver"; + private const string BitcodeFileName = "qir.bc"; + + public virtual string SourceDirectoryPath => "src"; + public abstract string DriverFileExtension { get; } + public abstract IList LinkLibraries { get; } + public abstract IList HeaderDirectories { get; } + public abstract IList LibraryDirectories { get; } + + protected FileInfo ExecutableFile { get; } + + private readonly byte[] qirBitcode; + private readonly ILogger? logger; + private readonly IQirExecutableRunner runner; + private readonly IQirDriverGenerator driverGenerator; + private readonly IQirExecutableGenerator executableGenerator; + + public QirExecutable(FileInfo executableFile, byte[] qirBitcode, IQirDriverGenerator driverGenerator, ILogger? logger = null) + : this(executableFile, + qirBitcode, + driverGenerator, + new QirExecutableGenerator(new ClangClient(logger), logger), + new QirExecutableRunner(logger), + logger) + { + } + + internal QirExecutable(FileInfo executableFile, byte[] qirBitcode, IQirDriverGenerator driverGenerator, IQirExecutableGenerator executableGenerator, IQirExecutableRunner runner, ILogger? logger) + { + ExecutableFile = executableFile; + this.qirBitcode = qirBitcode; + this.logger = logger; + this.runner = runner; + this.driverGenerator = driverGenerator; + this.executableGenerator = executableGenerator; + } + + public async Task BuildAsync(EntryPointOperation entryPoint, IList libraryDirectories, IList includeDirectories) + { + var sourceDirectory = new DirectoryInfo(SourceDirectoryPath); + if (sourceDirectory.Exists) + { + sourceDirectory.Delete(true); + } + + sourceDirectory.Create(); + logger?.LogInformation($"Created source directory at {sourceDirectory.FullName}."); + + // Create driver. + var driverFileNameWithExtension = Path.ChangeExtension(DriverFileName, DriverFileExtension); + var driverFile = new FileInfo(Path.Combine(sourceDirectory.FullName, driverFileNameWithExtension)); + using (var driverFileStream = driverFile.OpenWrite()) + { + await driverGenerator.GenerateAsync(entryPoint, driverFileStream); + } + logger?.LogInformation($"Created driver file at {driverFile.FullName}."); + + // Create bitcode file. + var bitcodeFile = new FileInfo(Path.Combine(sourceDirectory.FullName, BitcodeFileName)); + using (var bitcodeFileStream = bitcodeFile.OpenWrite()) + { + await bitcodeFileStream.WriteAsync(qirBitcode); + } + logger?.LogInformation($"Created bitcode file at {bitcodeFile.FullName}."); + await executableGenerator.GenerateExecutableAsync(ExecutableFile, sourceDirectory, libraryDirectories.Concat(this.LibraryDirectories).ToList(), includeDirectories.Concat(this.HeaderDirectories).ToList(), LinkLibraries); + } + + public async Task RunAsync(ExecutionInformation executionInformation, Stream output) + { + var stringArguments = driverGenerator.GetCommandLineArguments(executionInformation); + await runner.RunExecutableAsync(ExecutableFile, output, stringArguments); + } + } +} diff --git a/src/Qir/Execution/Tools/Executable/QirExecutableGenerator.cs b/src/Qir/Execution/Tools/Executable/QirExecutableGenerator.cs new file mode 100644 index 00000000000..6db49cb64a9 --- /dev/null +++ b/src/Qir/Execution/Tools/Executable/QirExecutableGenerator.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +#nullable enable + +namespace Microsoft.Quantum.Qir.Tools.Executable +{ + internal class QirExecutableGenerator : IQirExecutableGenerator + { + private readonly IClangClient clangClient; + private readonly ILogger? logger; + + public QirExecutableGenerator(IClangClient clangClient, ILogger? logger) + { + this.clangClient = clangClient; + this.logger = logger; + } + + public async Task GenerateExecutableAsync(FileInfo executableFile, DirectoryInfo sourceDirectory, IList libraryDirectories, IList includeDirectories, IList linkLibraries) + { + // Wrap in a Task.Run because FileInfo methods are not asynchronous. + await Task.Run(async () => + { + var binDirectory = executableFile.Directory; + logger?.LogInformation($"Creating binary directory at {binDirectory.FullName}."); + executableFile.Directory.Create(); + + // Copy all library contents to bin. + logger?.LogInformation("Copying library directories into the executable's folder."); + + if (!sourceDirectory.Exists) + { + logger?.LogWarning($"Cannot find source directory: {sourceDirectory.FullName}"); + } + + var libDirs = new List(); + foreach (var dir in libraryDirectories) + { + if (!dir.Exists) + { + logger?.LogWarning($"Cannot find given directory: {dir.FullName}"); + } + else + { + CopyDirectoryFilesIfNotExist(dir, binDirectory); + libDirs.Add(dir.FullName); + } + } + + var includeDirs = new List(); + foreach (var dir in includeDirectories) + { + if (!dir.Exists) + { + logger?.LogWarning($"Could not find given directory: {dir.FullName}"); + } + else + { + includeDirs.Add(dir.FullName); + } + } + + var inputFiles = sourceDirectory.GetFiles().Select(fileInfo => fileInfo.FullName).ToArray(); + await clangClient.CreateExecutableAsync(inputFiles, linkLibraries.ToArray(), libDirs.ToArray(), includeDirs.ToArray(), executableFile.FullName); + }); + } + + private void CopyFileIfNotExists(FileInfo fileToCopy, DirectoryInfo destinationDirectory) + { + var newPath = Path.Combine(destinationDirectory.FullName, fileToCopy.Name); + if (!File.Exists(newPath)) + { + var newFile = fileToCopy.CopyTo(newPath); + logger?.LogInformation($"Copied file {fileToCopy.FullName} to {newFile.FullName}"); + } + } + + private void CopyDirectoryFilesIfNotExist(DirectoryInfo directoryToCopy, DirectoryInfo destinationDirectory) + { + FileInfo[] files = directoryToCopy.GetFiles(); + foreach (var file in files) + { + CopyFileIfNotExists(file, destinationDirectory); + } + } + } +} diff --git a/src/Qir/Execution/Tools/Executable/QirExecutableRunner.cs b/src/Qir/Execution/Tools/Executable/QirExecutableRunner.cs new file mode 100644 index 00000000000..ea1a0d5a47f --- /dev/null +++ b/src/Qir/Execution/Tools/Executable/QirExecutableRunner.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +#nullable enable + +namespace Microsoft.Quantum.Qir.Tools.Executable +{ + internal class QirExecutableRunner : IQirExecutableRunner + { + private readonly ILogger? logger; + + public QirExecutableRunner(ILogger? logger) + { + this.logger = logger; + } + + public async Task RunExecutableAsync(FileInfo executableFile, Stream stream, string arguments) + { + logger?.LogInformation($"Invoking executable {executableFile.FullName} with the following arguments: {arguments}"); + using var process = new Process(); + process.StartInfo = new ProcessStartInfo + { + FileName = executableFile.FullName, + Arguments = arguments, + RedirectStandardOutput = true, + }; + + process.EnableRaisingEvents = true; + process.Start(); + var output = await process.StandardOutput.ReadToEndAsync(); + process.WaitForExit(); + using var streamWriter = new StreamWriter(stream); + await streamWriter.WriteAsync(output); + logger?.LogInformation($"Executable has finished running. Result code: {process.ExitCode}"); + } + } +} diff --git a/src/Qir/Execution/Tools/Executable/QirFullStateExecutable.cs b/src/Qir/Execution/Tools/Executable/QirFullStateExecutable.cs new file mode 100644 index 00000000000..4c4328ef34f --- /dev/null +++ b/src/Qir/Execution/Tools/Executable/QirFullStateExecutable.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using Microsoft.Extensions.Logging; +using Microsoft.Quantum.Qir.Tools.Driver; + +#nullable enable + +namespace Microsoft.Quantum.Qir.Tools.Executable +{ + /// + /// Class to create and run QIR-based executables that use the full-state simulator. + /// + public class QirFullStateExecutable : QirExecutable + { + public override string DriverFileExtension => "cpp"; + + public override IList LinkLibraries => new List { + "Microsoft.Quantum.Qir.Runtime", + "Microsoft.Quantum.Qir.QSharp.Foundation", + "Microsoft.Quantum.Qir.QSharp.Core" + }; + + public override IList HeaderDirectories { get; } = new List(); + + public override IList LibraryDirectories { get; } = new List(); + + public QirFullStateExecutable(FileInfo executableFile, byte[] qirBitcode, ILogger? logger = null) + : base(executableFile, + qirBitcode, + new QirFullStateDriverGenerator(), + logger) + { + var thisModulePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + if (string.IsNullOrWhiteSpace(thisModulePath)) + { + throw new InvalidOperationException("Could not get a path for the current assembly location."); + } + HeaderDirectories.Add(new DirectoryInfo(Path.Combine(thisModulePath, "runtimes", "any", "native", "include"))); + HeaderDirectories.Add(new DirectoryInfo(Path.Combine(thisModulePath, "Externals", "CLI11"))); + + var osID = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "win-x64" + : RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "linux-x64" + : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "osx-x64" + : throw new ArgumentException("Unsupported operating system architecture."); + + LibraryDirectories.Add(new DirectoryInfo(Path.Combine(thisModulePath, "runtimes", osID, "native"))); + LibraryDirectories.Add(new DirectoryInfo(Path.Combine(thisModulePath, "Libraries", osID))); + LibraryDirectories.Add(new DirectoryInfo(Path.Combine(thisModulePath, "Libraries"))); + } + } +} diff --git a/src/Qir/Execution/Tools/Externals/CLI11/CLI11.hpp b/src/Qir/Execution/Tools/Externals/CLI11/CLI11.hpp new file mode 100644 index 00000000000..68244d3864d --- /dev/null +++ b/src/Qir/Execution/Tools/Externals/CLI11/CLI11.hpp @@ -0,0 +1,8258 @@ +#pragma once + +// CLI11: Version 1.9.1 +// Originally designed by Henry Schreiner +// https://github.com/CLIUtils/CLI11 +// +// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts +// from: v1.9.1 +// +// From LICENSE: +// +// CLI11 1.8 Copyright (c) 2017-2019 University of Cincinnati, developed by Henry +// Schreiner under NSF AWARD 1414736. All rights reserved. +// +// Redistribution and use in source and binary forms of CLI11, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Standard combined includes: + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// Verbatim copy from Version.hpp: + + +#define CLI11_VERSION_MAJOR 1 +#define CLI11_VERSION_MINOR 9 +#define CLI11_VERSION_PATCH 1 +#define CLI11_VERSION "1.9.1" + + + + +// Verbatim copy from Macros.hpp: + + +// The following version macro is very similar to the one in PyBind11 +#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) +#if __cplusplus >= 201402L +#define CLI11_CPP14 +#if __cplusplus >= 201703L +#define CLI11_CPP17 +#if __cplusplus > 201703L +#define CLI11_CPP20 +#endif +#endif +#endif +#elif defined(_MSC_VER) && __cplusplus == 199711L +// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented) +// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer +#if _MSVC_LANG >= 201402L +#define CLI11_CPP14 +#if _MSVC_LANG > 201402L && _MSC_VER >= 1910 +#define CLI11_CPP17 +#if __MSVC_LANG > 201703L && _MSC_VER >= 1910 +#define CLI11_CPP20 +#endif +#endif +#endif +#endif + +#if defined(CLI11_CPP14) +#define CLI11_DEPRECATED(reason) [[deprecated(reason)]] +#elif defined(_MSC_VER) +#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) +#else +#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) +#endif + + + + +// Verbatim copy from Validators.hpp: + + +// C standard library +// Only needed for existence checking +#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM +#if __has_include() +// Filesystem cannot be used if targeting macOS < 10.15 +#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 +#define CLI11_HAS_FILESYSTEM 0 +#else +#include +#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 +#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9 +#define CLI11_HAS_FILESYSTEM 1 +#elif defined(__GLIBCXX__) +// if we are using gcc and Version <9 default to no filesystem +#define CLI11_HAS_FILESYSTEM 0 +#else +#define CLI11_HAS_FILESYSTEM 1 +#endif +#else +#define CLI11_HAS_FILESYSTEM 0 +#endif +#endif +#endif +#endif + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include // NOLINT(build/include) +#else +#include +#include +#endif + + + +// From Version.hpp: + + + +// From Macros.hpp: + + + +// From StringTools.hpp: + +namespace CLI { + +/// Include the items in this namespace to get free conversion of enums to/from streams. +/// (This is available inside CLI as well, so CLI11 will use this without a using statement). +namespace enums { + +/// output streaming for enumerations +template ::value>::type> +std::ostream &operator<<(std::ostream &in, const T &item) { + // make sure this is out of the detail namespace otherwise it won't be found when needed + return in << static_cast::type>(item); +} + +} // namespace enums + +/// Export to CLI namespace +using enums::operator<<; + +namespace detail { +/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not +/// produce overflow for some expected uses +constexpr int expected_max_vector_size{1 << 29}; +// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c +/// Split a string by a delim +inline std::vector split(const std::string &s, char delim) { + std::vector elems; + // Check to see if empty string, give consistent result + if(s.empty()) { + elems.emplace_back(); + } else { + std::stringstream ss; + ss.str(s); + std::string item; + while(std::getline(ss, item, delim)) { + elems.push_back(item); + } + } + return elems; +} + +/// Simple function to join a string +template std::string join(const T &v, std::string delim = ",") { + std::ostringstream s; + auto beg = std::begin(v); + auto end = std::end(v); + if(beg != end) + s << *beg++; + while(beg != end) { + s << delim << *beg++; + } + return s.str(); +} + +/// Simple function to join a string from processed elements +template ::value>::type> +std::string join(const T &v, Callable func, std::string delim = ",") { + std::ostringstream s; + auto beg = std::begin(v); + auto end = std::end(v); + if(beg != end) + s << func(*beg++); + while(beg != end) { + s << delim << func(*beg++); + } + return s.str(); +} + +/// Join a string in reverse order +template std::string rjoin(const T &v, std::string delim = ",") { + std::ostringstream s; + for(std::size_t start = 0; start < v.size(); start++) { + if(start > 0) + s << delim; + s << v[v.size() - start - 1]; + } + return s.str(); +} + +// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string + +/// Trim whitespace from left of string +inline std::string <rim(std::string &str) { + auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(str.begin(), it); + return str; +} + +/// Trim anything from left of string +inline std::string <rim(std::string &str, const std::string &filter) { + auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(str.begin(), it); + return str; +} + +/// Trim whitespace from right of string +inline std::string &rtrim(std::string &str) { + auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(it.base(), str.end()); + return str; +} + +/// Trim anything from right of string +inline std::string &rtrim(std::string &str, const std::string &filter) { + auto it = + std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(it.base(), str.end()); + return str; +} + +/// Trim whitespace from string +inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); } + +/// Trim anything from string +inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); } + +/// Make a copy of the string and then trim it +inline std::string trim_copy(const std::string &str) { + std::string s = str; + return trim(s); +} + +/// remove quotes at the front and back of a string either '"' or '\'' +inline std::string &remove_quotes(std::string &str) { + if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; +} + +/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) +inline std::string trim_copy(const std::string &str, const std::string &filter) { + std::string s = str; + return trim(s, filter); +} +/// Print a two part "help" string +inline std::ostream &format_help(std::ostream &out, std::string name, std::string description, std::size_t wid) { + name = " " + name; + out << std::setw(static_cast(wid)) << std::left << name; + if(!description.empty()) { + if(name.length() >= wid) + out << "\n" << std::setw(static_cast(wid)) << ""; + for(const char c : description) { + out.put(c); + if(c == '\n') { + out << std::setw(static_cast(wid)) << ""; + } + } + } + out << "\n"; + return out; +} + +/// Verify the first character of an option +template bool valid_first_char(T c) { + return std::isalnum(c, std::locale()) || c == '_' || c == '?' || c == '@'; +} + +/// Verify following characters of an option +template bool valid_later_char(T c) { return valid_first_char(c) || c == '.' || c == '-'; } + +/// Verify an option name +inline bool valid_name_string(const std::string &str) { + if(str.empty() || !valid_first_char(str[0])) + return false; + for(auto c : str.substr(1)) + if(!valid_later_char(c)) + return false; + return true; +} + +/// Verify that str consists of letters only +inline bool isalpha(const std::string &str) { + return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); }); +} + +/// Return a lower case version of a string +inline std::string to_lower(std::string str) { + std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { + return std::tolower(x, std::locale()); + }); + return str; +} + +/// remove underscores from a string +inline std::string remove_underscore(std::string str) { + str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str)); + return str; +} + +/// Find and replace a substring with another substring +inline std::string find_and_replace(std::string str, std::string from, std::string to) { + + std::size_t start_pos = 0; + + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; +} + +/// check if the flag definitions has possible false flags +inline bool has_default_flag_values(const std::string &flags) { + return (flags.find_first_of("{!") != std::string::npos); +} + +inline void remove_default_flag_values(std::string &flags) { + auto loc = flags.find_first_of('{'); + while(loc != std::string::npos) { + auto finish = flags.find_first_of("},", loc + 1); + if((finish != std::string::npos) && (flags[finish] == '}')) { + flags.erase(flags.begin() + static_cast(loc), + flags.begin() + static_cast(finish) + 1); + } + loc = flags.find_first_of('{', loc + 1); + } + flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end()); +} + +/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores +inline std::ptrdiff_t find_member(std::string name, + const std::vector names, + bool ignore_case = false, + bool ignore_underscore = false) { + auto it = std::end(names); + if(ignore_case) { + if(ignore_underscore) { + name = detail::to_lower(detail::remove_underscore(name)); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(detail::remove_underscore(local_name)) == name; + }); + } else { + name = detail::to_lower(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(local_name) == name; + }); + } + + } else if(ignore_underscore) { + name = detail::remove_underscore(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::remove_underscore(local_name) == name; + }); + } else { + it = std::find(std::begin(names), std::end(names), name); + } + + return (it != std::end(names)) ? (it - std::begin(names)) : (-1); +} + +/// Find a trigger string and call a modify callable function that takes the current string and starting position of the +/// trigger and returns the position in the string to search for the next trigger string +template inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) { + std::size_t start_pos = 0; + while((start_pos = str.find(trigger, start_pos)) != std::string::npos) { + start_pos = modify(str, start_pos); + } + return str; +} + +/// Split a string '"one two" "three"' into 'one two', 'three' +/// Quote characters can be ` ' or " +inline std::vector split_up(std::string str, char delimiter = '\0') { + + const std::string delims("\'\"`"); + auto find_ws = [delimiter](char ch) { + return (delimiter == '\0') ? (std::isspace(ch, std::locale()) != 0) : (ch == delimiter); + }; + trim(str); + + std::vector output; + bool embeddedQuote = false; + char keyChar = ' '; + while(!str.empty()) { + if(delims.find_first_of(str[0]) != std::string::npos) { + keyChar = str[0]; + auto end = str.find_first_of(keyChar, 1); + while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes + end = str.find_first_of(keyChar, end + 1); + embeddedQuote = true; + } + if(end != std::string::npos) { + output.push_back(str.substr(1, end - 1)); + str = str.substr(end + 1); + } else { + output.push_back(str.substr(1)); + str = ""; + } + } else { + auto it = std::find_if(std::begin(str), std::end(str), find_ws); + if(it != std::end(str)) { + std::string value = std::string(str.begin(), it); + output.push_back(value); + str = std::string(it + 1, str.end()); + } else { + output.push_back(str); + str = ""; + } + } + // transform any embedded quotes into the regular character + if(embeddedQuote) { + output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar)); + embeddedQuote = false; + } + trim(str); + } + return output; +} + +/// Add a leader to the beginning of all new lines (nothing is added +/// at the start of the first line). `"; "` would be for ini files +/// +/// Can't use Regex, or this would be a subs. +inline std::string fix_newlines(const std::string &leader, std::string input) { + std::string::size_type n = 0; + while(n != std::string::npos && n < input.size()) { + n = input.find('\n', n); + if(n != std::string::npos) { + input = input.substr(0, n + 1) + leader + input.substr(n + 1); + n += leader.size(); + } + } + return input; +} + +/// This function detects an equal or colon followed by an escaped quote after an argument +/// then modifies the string to replace the equality with a space. This is needed +/// to allow the split up function to work properly and is intended to be used with the find_and_modify function +/// the return value is the offset+1 which is required by the find_and_modify function. +inline std::size_t escape_detect(std::string &str, std::size_t offset) { + auto next = str[offset + 1]; + if((next == '\"') || (next == '\'') || (next == '`')) { + auto astart = str.find_last_of("-/ \"\'`", offset - 1); + if(astart != std::string::npos) { + if(str[astart] == ((str[offset] == '=') ? '-' : '/')) + str[offset] = ' '; // interpret this as a space so the split_up works properly + } + } + return offset + 1; +} + +/// Add quotes if the string contains spaces +inline std::string &add_quotes_if_needed(std::string &str) { + if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) { + char quote = str.find('"') < str.find('\'') ? '\'' : '"'; + if(str.find(' ') != std::string::npos) { + str.insert(0, 1, quote); + str.append(1, quote); + } + } + return str; +} + +} // namespace detail + +} // namespace CLI + +// From Error.hpp: + +namespace CLI { + +// Use one of these on all error classes. +// These are temporary and are undef'd at the end of this file. +#define CLI11_ERROR_DEF(parent, name) \ + protected: \ + name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \ + name(std::string ename, std::string msg, ExitCodes exit_code) \ + : parent(std::move(ename), std::move(msg), exit_code) {} \ + \ + public: \ + name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ + name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} + +// This is added after the one above if a class is used directly and builds its own message +#define CLI11_ERROR_SIMPLE(name) \ + explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {} + +/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, +/// int values from e.get_error_code(). +enum class ExitCodes { + Success = 0, + IncorrectConstruction = 100, + BadNameString, + OptionAlreadyAdded, + FileError, + ConversionError, + ValidationError, + RequiredError, + RequiresError, + ExcludesError, + ExtrasError, + ConfigError, + InvalidError, + HorribleError, + OptionNotFound, + ArgumentMismatch, + BaseClass = 127 +}; + +// Error definitions + +/// @defgroup error_group Errors +/// @brief Errors thrown by CLI11 +/// +/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. +/// @{ + +/// All errors derive from this one +class Error : public std::runtime_error { + int actual_exit_code; + std::string error_name{"Error"}; + + public: + int get_exit_code() const { return actual_exit_code; } + + std::string get_name() const { return error_name; } + + Error(std::string name, std::string msg, int exit_code = static_cast(ExitCodes::BaseClass)) + : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {} + + Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} +}; + +// Note: Using Error::Error constructors does not work on GCC 4.7 + +/// Construction errors (not in parsing) +class ConstructionError : public Error { + CLI11_ERROR_DEF(Error, ConstructionError) +}; + +/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) +class IncorrectConstruction : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) + CLI11_ERROR_SIMPLE(IncorrectConstruction) + static IncorrectConstruction PositionalFlag(std::string name) { + return IncorrectConstruction(name + ": Flags cannot be positional"); + } + static IncorrectConstruction Set0Opt(std::string name) { + return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); + } + static IncorrectConstruction SetFlag(std::string name) { + return IncorrectConstruction(name + ": Cannot set an expected number for flags"); + } + static IncorrectConstruction ChangeNotVector(std::string name) { + return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); + } + static IncorrectConstruction AfterMultiOpt(std::string name) { + return IncorrectConstruction( + name + ": You can't change expected arguments after you've changed the multi option policy!"); + } + static IncorrectConstruction MissingOption(std::string name) { + return IncorrectConstruction("Option " + name + " is not defined"); + } + static IncorrectConstruction MultiOptionPolicy(std::string name) { + return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); + } +}; + +/// Thrown on construction of a bad name +class BadNameString : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, BadNameString) + CLI11_ERROR_SIMPLE(BadNameString) + static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } + static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } + static BadNameString DashesOnly(std::string name) { + return BadNameString("Must have a name, not just dashes: " + name); + } + static BadNameString MultiPositionalNames(std::string name) { + return BadNameString("Only one positional name allowed, remove: " + name); + } +}; + +/// Thrown when an option already exists +class OptionAlreadyAdded : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) + explicit OptionAlreadyAdded(std::string name) + : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} + static OptionAlreadyAdded Requires(std::string name, std::string other) { + return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded); + } + static OptionAlreadyAdded Excludes(std::string name, std::string other) { + return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded); + } +}; + +// Parsing errors + +/// Anything that can error in Parse +class ParseError : public Error { + CLI11_ERROR_DEF(Error, ParseError) +}; + +// Not really "errors" + +/// This is a successful completion on parsing, supposed to exit +class Success : public ParseError { + CLI11_ERROR_DEF(ParseError, Success) + Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {} +}; + +/// -h or --help on command line +class CallForHelp : public ParseError { + CLI11_ERROR_DEF(ParseError, CallForHelp) + CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Usually something like --help-all on command line +class CallForAllHelp : public ParseError { + CLI11_ERROR_DEF(ParseError, CallForAllHelp) + CallForAllHelp() + : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. +class RuntimeError : public ParseError { + CLI11_ERROR_DEF(ParseError, RuntimeError) + explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} +}; + +/// Thrown when parsing an INI file and it is missing +class FileError : public ParseError { + CLI11_ERROR_DEF(ParseError, FileError) + CLI11_ERROR_SIMPLE(FileError) + static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); } +}; + +/// Thrown when conversion call back fails, such as when an int fails to coerce to a string +class ConversionError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConversionError) + CLI11_ERROR_SIMPLE(ConversionError) + ConversionError(std::string member, std::string name) + : ConversionError("The value " + member + " is not an allowed value for " + name) {} + ConversionError(std::string name, std::vector results) + : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {} + static ConversionError TooManyInputsFlag(std::string name) { + return ConversionError(name + ": too many inputs for a flag"); + } + static ConversionError TrueFalse(std::string name) { + return ConversionError(name + ": Should be true/false or a number"); + } +}; + +/// Thrown when validation of results fails +class ValidationError : public ParseError { + CLI11_ERROR_DEF(ParseError, ValidationError) + CLI11_ERROR_SIMPLE(ValidationError) + explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {} +}; + +/// Thrown when a required option is missing +class RequiredError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiredError) + explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} + static RequiredError Subcommand(std::size_t min_subcom) { + if(min_subcom == 1) { + return RequiredError("A subcommand"); + } + return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", + ExitCodes::RequiredError); + } + static RequiredError + Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) { + if((min_option == 1) && (max_option == 1) && (used == 0)) + return RequiredError("Exactly 1 option from [" + option_list + "]"); + if((min_option == 1) && (max_option == 1) && (used > 1)) { + return RequiredError("Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) + + " were given", + ExitCodes::RequiredError); + } + if((min_option == 1) && (used == 0)) + return RequiredError("At least 1 option from [" + option_list + "]"); + if(used < min_option) { + return RequiredError("Requires at least " + std::to_string(min_option) + " options used and only " + + std::to_string(used) + "were given from [" + option_list + "]", + ExitCodes::RequiredError); + } + if(max_option == 1) + return RequiredError("Requires at most 1 options be given from [" + option_list + "]", + ExitCodes::RequiredError); + + return RequiredError("Requires at most " + std::to_string(max_option) + " options be used and " + + std::to_string(used) + "were given from [" + option_list + "]", + ExitCodes::RequiredError); + } +}; + +/// Thrown when the wrong number of arguments has been received +class ArgumentMismatch : public ParseError { + CLI11_ERROR_DEF(ParseError, ArgumentMismatch) + CLI11_ERROR_SIMPLE(ArgumentMismatch) + ArgumentMismatch(std::string name, int expected, std::size_t received) + : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + + ", got " + std::to_string(received)) + : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + + ", got " + std::to_string(received)), + ExitCodes::ArgumentMismatch) {} + + static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) { + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) { + return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); + } + static ArgumentMismatch FlagOverride(std::string name) { + return ArgumentMismatch(name + " was given a disallowed flag override"); + } +}; + +/// Thrown when a requires option is missing +class RequiresError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiresError) + RequiresError(std::string curname, std::string subname) + : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} +}; + +/// Thrown when an excludes option is present +class ExcludesError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExcludesError) + ExcludesError(std::string curname, std::string subname) + : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} +}; + +/// Thrown when too many positionals or options are found +class ExtrasError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExtrasError) + explicit ExtrasError(std::vector args) + : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} + ExtrasError(const std::string &name, std::vector args) + : ExtrasError(name, + (args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} +}; + +/// Thrown when extra values are found in an INI file +class ConfigError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConfigError) + CLI11_ERROR_SIMPLE(ConfigError) + static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); } + static ConfigError NotConfigurable(std::string item) { + return ConfigError(item + ": This option is not allowed in a configuration file"); + } +}; + +/// Thrown when validation fails before parsing +class InvalidError : public ParseError { + CLI11_ERROR_DEF(ParseError, InvalidError) + explicit InvalidError(std::string name) + : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { + } +}; + +/// This is just a safety check to verify selection and parsing match - you should not ever see it +/// Strings are directly added to this error, but again, it should never be seen. +class HorribleError : public ParseError { + CLI11_ERROR_DEF(ParseError, HorribleError) + CLI11_ERROR_SIMPLE(HorribleError) +}; + +// After parsing + +/// Thrown when counting a non-existent option +class OptionNotFound : public Error { + CLI11_ERROR_DEF(Error, OptionNotFound) + explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} +}; + +#undef CLI11_ERROR_DEF +#undef CLI11_ERROR_SIMPLE + +/// @} + +} // namespace CLI + +// From TypeTools.hpp: + +namespace CLI { + +// Type tools + +// Utilities for type enabling +namespace detail { +// Based generally on https://rmf.io/cxx11/almost-static-if +/// Simple empty scoped class +enum class enabler {}; + +/// An instance to use in EnableIf +constexpr enabler dummy = {}; +} // namespace detail + +/// A copy of enable_if_t from C++14, compatible with C++11. +/// +/// We could check to see if C++14 is being used, but it does not hurt to redefine this +/// (even Google does this: https://github.com/google/skia/blob/master/include/private/SkTLogic.h) +/// It is not in the std namespace anyway, so no harm done. +template using enable_if_t = typename std::enable_if::type; + +/// A copy of std::void_t from C++17 (helper for C++11 and C++14) +template struct make_void { using type = void; }; + +/// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine +template using void_t = typename make_void::type; + +/// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine +template using conditional_t = typename std::conditional::type; + +/// Check to see if something is a vector (fail check by default) +template struct is_vector : std::false_type {}; + +/// Check to see if something is a vector (true if actually a vector) +template struct is_vector> : std::true_type {}; + +/// Check to see if something is a vector (true if actually a const vector) +template struct is_vector> : std::true_type {}; + +/// Check to see if something is bool (fail check by default) +template struct is_bool : std::false_type {}; + +/// Check to see if something is bool (true if actually a bool) +template <> struct is_bool : std::true_type {}; + +/// Check to see if something is a shared pointer +template struct is_shared_ptr : std::false_type {}; + +/// Check to see if something is a shared pointer (True if really a shared pointer) +template struct is_shared_ptr> : std::true_type {}; + +/// Check to see if something is a shared pointer (True if really a shared pointer) +template struct is_shared_ptr> : std::true_type {}; + +/// Check to see if something is copyable pointer +template struct is_copyable_ptr { + static bool const value = is_shared_ptr::value || std::is_pointer::value; +}; + +/// This can be specialized to override the type deduction for IsMember. +template struct IsMemberType { using type = T; }; + +/// The main custom type needed here is const char * should be a string. +template <> struct IsMemberType { using type = std::string; }; + +namespace detail { + +// These are utilities for IsMember and other transforming objects + +/// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that +/// pointer_traits be valid. + +/// not a pointer +template struct element_type { using type = T; }; + +template struct element_type::value>::type> { + using type = typename std::pointer_traits::element_type; +}; + +/// Combination of the element type and value type - remove pointer (including smart pointers) and get the value_type of +/// the container +template struct element_value_type { using type = typename element_type::type::value_type; }; + +/// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing. +template struct pair_adaptor : std::false_type { + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; + + /// Get the first value (really just the underlying value) + template static auto first(Q &&pair_value) -> decltype(std::forward(pair_value)) { + return std::forward(pair_value); + } + /// Get the second value (really just the underlying value) + template static auto second(Q &&pair_value) -> decltype(std::forward(pair_value)) { + return std::forward(pair_value); + } +}; + +/// Adaptor for map-like structure (true version, must have key_type and mapped_type). +/// This wraps a mapped container in a few utilities access it in a general way. +template +struct pair_adaptor< + T, + conditional_t, void>> + : std::true_type { + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; + + /// Get the first value (really just the underlying value) + template static auto first(Q &&pair_value) -> decltype(std::get<0>(std::forward(pair_value))) { + return std::get<0>(std::forward(pair_value)); + } + /// Get the second value (really just the underlying value) + template static auto second(Q &&pair_value) -> decltype(std::get<1>(std::forward(pair_value))) { + return std::get<1>(std::forward(pair_value)); + } +}; + +// Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a Wnarrowing warning +// in the unevaluated context even if the function that was using this wasn't used. The standard says narrowing in +// brace initialization shouldn't be allowed but for backwards compatibility gcc allows it in some contexts. It is a +// little fuzzy what happens in template constructs and I think that was something GCC took a little while to work out. +// But regardless some versions of gcc generate a warning when they shouldn't from the following code so that should be +// suppressed +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnarrowing" +#endif +// check for constructibility from a specific type and copy assignable used in the parse detection +template class is_direct_constructible { + template + static auto test(int, std::true_type) -> decltype( +// NVCC warns about narrowing conversions here +#ifdef __CUDACC__ +#pragma diag_suppress 2361 +#endif + TT { std::declval() } +#ifdef __CUDACC__ +#pragma diag_default 2361 +#endif + , + std::is_move_assignable()); + + template static auto test(int, std::false_type) -> std::false_type; + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0, typename std::is_constructible::type()))::value; +}; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +// Check for output streamability +// Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream + +template class is_ostreamable { + template + static auto test(int) -> decltype(std::declval() << std::declval(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Check for input streamability +template class is_istreamable { + template + static auto test(int) -> decltype(std::declval() >> std::declval(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Templated operation to get a value from a stream +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string &istring, T &obj) { + std::istringstream is; + is.str(istring); + is >> obj; + return !is.fail() && !is.rdbuf()->in_avail(); +} + +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string & /*istring*/, T & /*obj*/) { + return false; +} + +// Check for tuple like types, as in classes with a tuple_size type trait +template class is_tuple_like { + template + // static auto test(int) + // -> decltype(std::conditional<(std::tuple_size::value > 0), std::true_type, std::false_type>::type()); + static auto test(int) -> decltype(std::tuple_size::value, std::true_type{}); + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Convert an object to a string (directly forward if this can become a string) +template ::value, detail::enabler> = detail::dummy> +auto to_string(T &&value) -> decltype(std::forward(value)) { + return std::forward(value); +} + +/// Construct a string from the object +template ::value && !std::is_convertible::value, + detail::enabler> = detail::dummy> +std::string to_string(const T &value) { + return std::string(value); +} + +/// Convert an object to a string (streaming must be supported for that type) +template ::value && !std::is_constructible::value && + is_ostreamable::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&value) { + std::stringstream stream; + stream << value; + return stream.str(); +} + +/// If conversion is not supported, return an empty string (streaming is not supported for that type) +template ::value && !is_ostreamable::value && + !is_vector::type>::type>::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&) { + return std::string{}; +} + +/// convert a vector to a string +template ::value && !is_ostreamable::value && + is_vector::type>::type>::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&variable) { + std::vector defaults; + defaults.reserve(variable.size()); + auto cval = variable.begin(); + auto end = variable.end(); + while(cval != end) { + defaults.emplace_back(CLI::detail::to_string(*cval)); + ++cval; + } + return std::string("[" + detail::join(defaults) + "]"); +} + +/// special template overload +template ::value, detail::enabler> = detail::dummy> +auto checked_to_string(T &&value) -> decltype(to_string(std::forward(value))) { + return to_string(std::forward(value)); +} + +/// special template overload +template ::value, detail::enabler> = detail::dummy> +std::string checked_to_string(T &&) { + return std::string{}; +} +/// get a string as a convertible value for arithmetic types +template ::value, detail::enabler> = detail::dummy> +std::string value_string(const T &value) { + return std::to_string(value); +} +/// get a string as a convertible value for enumerations +template ::value, detail::enabler> = detail::dummy> +std::string value_string(const T &value) { + return std::to_string(static_cast::type>(value)); +} +/// for other types just use the regular to_string function +template ::value && !std::is_arithmetic::value, detail::enabler> = detail::dummy> +auto value_string(const T &value) -> decltype(to_string(value)) { + return to_string(value); +} + +/// This will only trigger for actual void type +template struct type_count { static const int value{0}; }; + +/// Set of overloads to get the type size of an object +template struct type_count::value>::type> { + static constexpr int value{std::tuple_size::value}; +}; +/// Type size for regular object types that do not look like a tuple +template +struct type_count< + T, + typename std::enable_if::value && !is_tuple_like::value && !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// Type size of types that look like a vector +template struct type_count::value>::type> { + static constexpr int value{is_vector::value ? expected_max_vector_size + : type_count::value}; +}; + +/// This will only trigger for actual void type +template struct expected_count { static const int value{0}; }; + +/// For most types the number of expected items is 1 +template +struct expected_count::value && !std::is_void::value>::type> { + static constexpr int value{1}; +}; +/// number of expected items in a vector +template struct expected_count::value>::type> { + static constexpr int value{expected_max_vector_size}; +}; + +// Enumeration of the different supported categorizations of objects +enum class object_category : int { + integral_value = 2, + unsigned_integral = 4, + enumeration = 6, + boolean_value = 8, + floating_point = 10, + number_constructible = 12, + double_constructible = 14, + integer_constructible = 16, + vector_value = 30, + tuple_value = 35, + // string assignable or greater used in a condition so anything string like must come last + string_assignable = 50, + string_constructible = 60, + other = 200, + +}; + +/// some type that is not otherwise recognized +template struct classify_object { + static constexpr object_category value{object_category::other}; +}; + +/// Set of overloads to classify an object according to type +template +struct classify_object::value && std::is_signed::value && + !is_bool::value && !std::is_enum::value>::type> { + static constexpr object_category value{object_category::integral_value}; +}; + +/// Unsigned integers +template +struct classify_object< + T, + typename std::enable_if::value && std::is_unsigned::value && !is_bool::value>::type> { + static constexpr object_category value{object_category::unsigned_integral}; +}; + +/// Boolean values +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::boolean_value}; +}; + +/// Floats +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::floating_point}; +}; + +/// String and similar direct assignment +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + std::is_assignable::value && !is_vector::value>::type> { + static constexpr object_category value{object_category::string_assignable}; +}; + +/// String and similar constructible and copy assignment +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + !std::is_assignable::value && + std::is_constructible::value && !is_vector::value>::type> { + static constexpr object_category value{object_category::string_constructible}; +}; + +/// Enumerations +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::enumeration}; +}; + +/// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, +/// vectors, and enumerations +template struct uncommon_type { + using type = typename std::conditional::value && !std::is_integral::value && + !std::is_assignable::value && + !std::is_constructible::value && !is_vector::value && + !std::is_enum::value, + std::true_type, + std::false_type>::type; + static constexpr bool value = type::value; +}; + +/// Assignable from double or int +template +struct classify_object::value && type_count::value == 1 && + is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::number_constructible}; +}; + +/// Assignable from int +template +struct classify_object::value && type_count::value == 1 && + !is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::integer_constructible}; +}; + +/// Assignable from double +template +struct classify_object::value && type_count::value == 1 && + is_direct_constructible::value && + !is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::double_constructible}; +}; + +/// Tuple type +template +struct classify_object::value >= 2 && !is_vector::value) || + (is_tuple_like::value && uncommon_type::value && + !is_direct_constructible::value && + !is_direct_constructible::value)>::type> { + static constexpr object_category value{object_category::tuple_value}; +}; + +/// Vector type +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::vector_value}; +}; + +// Type name print + +/// Was going to be based on +/// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template +/// But this is cleaner and works better in this case + +template ::value == object_category::integral_value || + classify_object::value == object_category::integer_constructible, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "INT"; +} + +template ::value == object_category::unsigned_integral, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "UINT"; +} + +template ::value == object_category::floating_point || + classify_object::value == object_category::number_constructible || + classify_object::value == object_category::double_constructible, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "FLOAT"; +} + +/// Print name for enumeration types +template ::value == object_category::enumeration, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "ENUM"; +} + +/// Print name for enumeration types +template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "BOOLEAN"; +} + +/// Print for all other types +template ::value >= object_category::string_assignable, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "TEXT"; +} + +/// Print name for single element tuple types +template ::value == object_category::tuple_value && type_count::value == 1, + detail::enabler> = detail::dummy> +inline std::string type_name() { + return type_name::type>(); +} + +/// Empty string if the index > tuple size +template +inline typename std::enable_if::value, std::string>::type tuple_name() { + return std::string{}; +} + +/// Recursively generate the tuple type name +template + inline typename std::enable_if < I::value, std::string>::type tuple_name() { + std::string str = std::string(type_name::type>()) + ',' + tuple_name(); + if(str.back() == ',') + str.pop_back(); + return str; +} + +/// Print type name for tuples with 2 or more elements +template ::value == object_category::tuple_value && type_count::value >= 2, + detail::enabler> = detail::dummy> +std::string type_name() { + auto tname = std::string(1, '[') + tuple_name(); + tname.push_back(']'); + return tname; +} + +/// This one should not be used normally, since vector types print the internal type +template ::value == object_category::vector_value, detail::enabler> = detail::dummy> +inline std::string type_name() { + return type_name(); +} + +// Lexical cast + +/// Convert a flag into an integer value typically binary flags +inline std::int64_t to_flag_value(std::string val) { + static const std::string trueString("true"); + static const std::string falseString("false"); + if(val == trueString) { + return 1; + } + if(val == falseString) { + return -1; + } + val = detail::to_lower(val); + std::int64_t ret; + if(val.size() == 1) { + if(val[0] >= '1' && val[0] <= '9') { + return (static_cast(val[0]) - '0'); + } + switch(val[0]) { + case '0': + case 'f': + case 'n': + case '-': + ret = -1; + break; + case 't': + case 'y': + case '+': + ret = 1; + break; + default: + throw std::invalid_argument("unrecognized character"); + } + return ret; + } + if(val == trueString || val == "on" || val == "yes" || val == "enable") { + ret = 1; + } else if(val == falseString || val == "off" || val == "no" || val == "disable") { + ret = -1; + } else { + ret = std::stoll(val); + } + return ret; +} + +/// Signed integers +template ::value == object_category::integral_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + try { + std::size_t n = 0; + std::int64_t output_ll = std::stoll(input, &n, 0); + output = static_cast(output_ll); + return n == input.size() && static_cast(output) == output_ll; + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// Unsigned integers +template ::value == object_category::unsigned_integral, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + if(!input.empty() && input.front() == '-') + return false; // std::stoull happily converts negative values to junk without any errors. + + try { + std::size_t n = 0; + std::uint64_t output_ll = std::stoull(input, &n, 0); + output = static_cast(output_ll); + return n == input.size() && static_cast(output) == output_ll; + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// Boolean values +template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + try { + auto out = to_flag_value(input); + output = (out > 0); + return true; + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + // if the number is out of the range of a 64 bit value then it is still a number and for this purpose is still + // valid all we care about the sign + output = (input[0] != '-'); + return true; + } +} + +/// Floats +template ::value == object_category::floating_point, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + try { + std::size_t n = 0; + output = static_cast(std::stold(input, &n)); + return n == input.size(); + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// String and similar direct assignment +template ::value == object_category::string_assignable, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = input; + return true; +} + +/// String and similar constructible and copy assignment +template < + typename T, + enable_if_t::value == object_category::string_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = T(input); + return true; +} + +/// Enumerations +template ::value == object_category::enumeration, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename std::underlying_type::type val; + bool retval = detail::lexical_cast(input, val); + if(!retval) { + return false; + } + output = static_cast(val); + return true; +} + +/// Assignable from double or int +template < + typename T, + enable_if_t::value == object_category::number_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val; + if(lexical_cast(input, val)) { + output = T(val); + return true; + } else { + double dval; + if(lexical_cast(input, dval)) { + output = T{dval}; + return true; + } + } + return from_stream(input, output); +} + +/// Assignable from int +template < + typename T, + enable_if_t::value == object_category::integer_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val; + if(lexical_cast(input, val)) { + output = T(val); + return true; + } + return from_stream(input, output); +} + +/// Assignable from double +template < + typename T, + enable_if_t::value == object_category::double_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + double val; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + +/// Non-string parsable by a stream +template ::value == object_category::other, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + static_assert(is_istreamable::value, + "option object type must have a lexical cast overload or streaming input operator(>>) defined, if it " + "is convertible from another type use the add_option(...) with XC being the known type"); + return from_stream(input, output); +} + +/// Assign a value through lexical cast operations +template < + typename T, + typename XC, + enable_if_t::value && (classify_object::value == object_category::string_assignable || + classify_object::value == object_category::string_constructible), + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, T &output) { + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations +template ::value && classify_object::value != object_category::string_assignable && + classify_object::value != object_category::string_constructible, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, T &output) { + if(input.empty()) { + output = T{}; + return true; + } + return lexical_cast(input, output); +} + +/// Assign a value converted from a string in lexical cast to the output value directly +template < + typename T, + typename XC, + enable_if_t::value && std::is_assignable::value, detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, T &output) { + XC val{}; + bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true; + if(parse_result) { + output = val; + } + return parse_result; +} + +/// Assign a value from a lexical cast through constructing a value and move assigning it +template ::value && !std::is_assignable::value && + std::is_move_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, T &output) { + XC val{}; + bool parse_result = input.empty() ? true : lexical_cast(input, val); + if(parse_result) { + output = T(val); // use () form of constructor to allow some implicit conversions + } + return parse_result; +} +/// Lexical conversion if there is only one element +template < + typename T, + typename XC, + enable_if_t::value && !is_tuple_like::value && !is_vector::value && !is_vector::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + return lexical_assign(strings[0], output); +} + +/// Lexical conversion if there is only one element but the conversion type is for two call a two element constructor +template ::value == 1 && type_count::value == 2, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + typename std::tuple_element<0, XC>::type v1; + typename std::tuple_element<1, XC>::type v2; + bool retval = lexical_assign(strings[0], v1); + if(strings.size() > 1) { + retval = retval && lexical_assign(strings[1], v2); + } + if(retval) { + output = T{v1, v2}; + } + return retval; +} + +/// Lexical conversion of a vector types +template ::value == expected_max_vector_size && + expected_count::value == expected_max_vector_size && type_count::value == 1, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + output.clear(); + output.reserve(strings.size()); + for(const auto &elem : strings) { + + output.emplace_back(); + bool retval = lexical_assign(elem, output.back()); + if(!retval) { + return false; + } + } + return (!output.empty()); +} + +/// Lexical conversion of a vector types with type size of two +template ::value == expected_max_vector_size && + expected_count::value == expected_max_vector_size && type_count::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + output.clear(); + for(std::size_t ii = 0; ii < strings.size(); ii += 2) { + + typename std::tuple_element<0, typename XC::value_type>::type v1; + typename std::tuple_element<1, typename XC::value_type>::type v2; + bool retval = lexical_assign(strings[ii], v1); + if(strings.size() > ii + 1) { + retval = retval && lexical_assign(strings[ii + 1], v2); + } + if(retval) { + output.emplace_back(v1, v2); + } else { + return false; + } + } + return (!output.empty()); +} + +/// Conversion to a vector type using a particular single type as the conversion type +template ::value == expected_max_vector_size) && (expected_count::value == 1) && + (type_count::value == 1), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + bool retval = true; + output.clear(); + output.reserve(strings.size()); + for(const auto &elem : strings) { + + output.emplace_back(); + retval = retval && lexical_assign(elem, output.back()); + } + return (!output.empty()) && retval; +} +// This one is last since it can call other lexical_conversion functions +/// Lexical conversion if there is only one element but the conversion type is a vector +template ::value && !is_vector::value && is_vector::value, detail::enabler> = + detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + + if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) { + XC val; + auto retval = lexical_conversion(strings, val); + output = T{val}; + return retval; + } + output = T{}; + return true; +} + +/// function template for converting tuples if the static Index is greater than the tuple size +template +inline typename std::enable_if= type_count::value, bool>::type tuple_conversion(const std::vector &, + T &) { + return true; +} +/// Tuple conversion operation +template + inline typename std::enable_if < + I::value, bool>::type tuple_conversion(const std::vector &strings, T &output) { + bool retval = true; + if(strings.size() > I) { + retval = retval && lexical_assign::type, + typename std::conditional::value, + typename std::tuple_element::type, + XC>::type>(strings[I], std::get(output)); + } + retval = retval && tuple_conversion(strings, output); + return retval; +} + +/// Conversion for tuples +template ::value, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + static_assert( + !is_tuple_like::value || type_count::value == type_count::value, + "if the conversion type is defined as a tuple it must be the same size as the type you are converting to"); + return tuple_conversion(strings, output); +} + +/// Lexical conversion of a vector types with type_size >2 +template ::value == expected_max_vector_size && + expected_count::value == expected_max_vector_size && (type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + bool retval = true; + output.clear(); + std::vector temp; + std::size_t ii = 0; + std::size_t icount = 0; + std::size_t xcm = type_count::value; + while(ii < strings.size()) { + temp.push_back(strings[ii]); + ++ii; + ++icount; + if(icount == xcm || temp.back().empty()) { + if(static_cast(xcm) == expected_max_vector_size) { + temp.pop_back(); + } + output.emplace_back(); + retval = retval && lexical_conversion(temp, output.back()); + temp.clear(); + if(!retval) { + return false; + } + icount = 0; + } + } + return retval; +} +/// Sum a vector of flag representations +/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is +/// by +/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most +/// common true and false strings then uses stoll to convert the rest for summing +template ::value && std::is_unsigned::value, detail::enabler> = detail::dummy> +void sum_flag_vector(const std::vector &flags, T &output) { + std::int64_t count{0}; + for(auto &flag : flags) { + count += detail::to_flag_value(flag); + } + output = (count > 0) ? static_cast(count) : T{0}; +} + +/// Sum a vector of flag representations +/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is +/// by +/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most +/// common true and false strings then uses stoll to convert the rest for summing +template ::value && std::is_signed::value, detail::enabler> = detail::dummy> +void sum_flag_vector(const std::vector &flags, T &output) { + std::int64_t count{0}; + for(auto &flag : flags) { + count += detail::to_flag_value(flag); + } + output = static_cast(count); +} + +} // namespace detail +} // namespace CLI + +// From Split.hpp: + +namespace CLI { +namespace detail { + +// Returns false if not a short option. Otherwise, sets opt name and rest and returns true +inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) { + if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { + name = current.substr(1, 1); + rest = current.substr(2); + return true; + } + return false; +} + +// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true +inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) { + auto loc = current.find_first_of('='); + if(loc != std::string::npos) { + name = current.substr(2, loc - 2); + value = current.substr(loc + 1); + } else { + name = current.substr(2); + value = ""; + } + return true; + } + return false; +} + +// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true +inline bool split_windows_style(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) { + auto loc = current.find_first_of(':'); + if(loc != std::string::npos) { + name = current.substr(1, loc - 1); + value = current.substr(loc + 1); + } else { + name = current.substr(1); + value = ""; + } + return true; + } + return false; +} + +// Splits a string into multiple long and short names +inline std::vector split_names(std::string current) { + std::vector output; + std::size_t val; + while((val = current.find(",")) != std::string::npos) { + output.push_back(trim_copy(current.substr(0, val))); + current = current.substr(val + 1); + } + output.push_back(trim_copy(current)); + return output; +} + +/// extract default flag values either {def} or starting with a ! +inline std::vector> get_default_flag_values(const std::string &str) { + std::vector flags = split_names(str); + flags.erase(std::remove_if(flags.begin(), + flags.end(), + [](const std::string &name) { + return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) && + (name.back() == '}')) || + (name[0] == '!')))); + }), + flags.end()); + std::vector> output; + output.reserve(flags.size()); + for(auto &flag : flags) { + auto def_start = flag.find_first_of('{'); + std::string defval = "false"; + if((def_start != std::string::npos) && (flag.back() == '}')) { + defval = flag.substr(def_start + 1); + defval.pop_back(); + flag.erase(def_start, std::string::npos); + } + flag.erase(0, flag.find_first_not_of("-!")); + output.emplace_back(flag, defval); + } + return output; +} + +/// Get a vector of short names, one of long names, and a single name +inline std::tuple, std::vector, std::string> +get_names(const std::vector &input) { + + std::vector short_names; + std::vector long_names; + std::string pos_name; + + for(std::string name : input) { + if(name.length() == 0) { + continue; + } + if(name.length() > 1 && name[0] == '-' && name[1] != '-') { + if(name.length() == 2 && valid_first_char(name[1])) + short_names.emplace_back(1, name[1]); + else + throw BadNameString::OneCharName(name); + } else if(name.length() > 2 && name.substr(0, 2) == "--") { + name = name.substr(2); + if(valid_name_string(name)) + long_names.push_back(name); + else + throw BadNameString::BadLongName(name); + } else if(name == "-" || name == "--") { + throw BadNameString::DashesOnly(name); + } else { + if(pos_name.length() > 0) + throw BadNameString::MultiPositionalNames(name); + pos_name = name; + } + } + + return std::tuple, std::vector, std::string>( + short_names, long_names, pos_name); +} + +} // namespace detail +} // namespace CLI + +// From ConfigFwd.hpp: + +namespace CLI { + +class App; + +/// Holds values to load into Options +struct ConfigItem { + /// This is the list of parents + std::vector parents{}; + + /// This is the name + std::string name{}; + + /// Listing of inputs + std::vector inputs{}; + + /// The list of parents and name joined by "." + std::string fullname() const { + std::vector tmp = parents; + tmp.emplace_back(name); + return detail::join(tmp, "."); + } +}; + +/// This class provides a converter for configuration files. +class Config { + protected: + std::vector items{}; + + public: + /// Convert an app into a configuration + virtual std::string to_config(const App *, bool, bool, std::string) const = 0; + + /// Convert a configuration into an app + virtual std::vector from_config(std::istream &) const = 0; + + /// Get a flag value + virtual std::string to_flag(const ConfigItem &item) const { + if(item.inputs.size() == 1) { + return item.inputs.at(0); + } + throw ConversionError::TooManyInputsFlag(item.fullname()); + } + + /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure + std::vector from_file(const std::string &name) { + std::ifstream input{name}; + if(!input.good()) + throw FileError::Missing(name); + + return from_config(input); + } + + /// Virtual destructor + virtual ~Config() = default; +}; + +/// This converter works with INI/TOML files; to write proper TOML files use ConfigTOML +class ConfigBase : public Config { + protected: + /// the character used for comments + char commentChar = ';'; + /// the character used to start an array '\0' is a default to not use + char arrayStart = '\0'; + /// the character used to end an array '\0' is a default to not use + char arrayEnd = '\0'; + /// the character used to separate elements in an array + char arraySeparator = ' '; + /// the character used separate the name from the value + char valueDelimiter = '='; + + public: + std::string + to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override; + + std::vector from_config(std::istream &input) const override; + /// Specify the configuration for comment characters + ConfigBase *comment(char cchar) { + commentChar = cchar; + return this; + } + /// Specify the start and end characters for an array + ConfigBase *arrayBounds(char aStart, char aEnd) { + arrayStart = aStart; + arrayEnd = aEnd; + return this; + } + /// Specify the delimiter character for an array + ConfigBase *arrayDelimiter(char aSep) { + arraySeparator = aSep; + return this; + } + /// Specify the delimiter between a name and value + ConfigBase *valueSeparator(char vSep) { + valueDelimiter = vSep; + return this; + } +}; + +/// the default Config is the INI file format +using ConfigINI = ConfigBase; + +/// ConfigTOML generates a TOML compliant output +class ConfigTOML : public ConfigINI { + + public: + ConfigTOML() { + commentChar = '#'; + arrayStart = '['; + arrayEnd = ']'; + arraySeparator = ','; + valueDelimiter = '='; + } +}; +} // namespace CLI + +// From Validators.hpp: + +namespace CLI { + +class Option; + +/// @defgroup validator_group Validators + +/// @brief Some validators that are provided +/// +/// These are simple `std::string(const std::string&)` validators that are useful. They return +/// a string if the validation fails. A custom struct is provided, as well, with the same user +/// semantics, but with the ability to provide a new type name. +/// @{ + +/// +class Validator { + protected: + /// This is the description function, if empty the description_ will be used + std::function desc_function_{[]() { return std::string{}; }}; + + /// This is the base function that is to be called. + /// Returns a string error message if validation fails. + std::function func_{[](std::string &) { return std::string{}; }}; + /// The name for search purposes of the Validator + std::string name_{}; + /// A Validator will only apply to an indexed value (-1 is all elements) + int application_index_ = -1; + /// Enable for Validator to allow it to be disabled if need be + bool active_{true}; + /// specify that a validator should not modify the input + bool non_modifying_{false}; + + public: + Validator() = default; + /// Construct a Validator with just the description string + explicit Validator(std::string validator_desc) : desc_function_([validator_desc]() { return validator_desc; }) {} + /// Construct Validator from basic information + Validator(std::function op, std::string validator_desc, std::string validator_name = "") + : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(op)), + name_(std::move(validator_name)) {} + /// Set the Validator operation function + Validator &operation(std::function op) { + func_ = std::move(op); + return *this; + } + /// This is the required operator for a Validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(std::string &str) const { + std::string retstring; + if(active_) { + if(non_modifying_) { + std::string value = str; + retstring = func_(value); + } else { + retstring = func_(str); + } + } + return retstring; + } + + /// This is the required operator for a Validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(const std::string &str) const { + std::string value = str; + return (active_) ? func_(value) : std::string{}; + } + + /// Specify the type string + Validator &description(std::string validator_desc) { + desc_function_ = [validator_desc]() { return validator_desc; }; + return *this; + } + /// Specify the type string + Validator description(std::string validator_desc) const { + Validator newval(*this); + newval.desc_function_ = [validator_desc]() { return validator_desc; }; + return newval; + } + /// Generate type description information for the Validator + std::string get_description() const { + if(active_) { + return desc_function_(); + } + return std::string{}; + } + /// Specify the type string + Validator &name(std::string validator_name) { + name_ = std::move(validator_name); + return *this; + } + /// Specify the type string + Validator name(std::string validator_name) const { + Validator newval(*this); + newval.name_ = std::move(validator_name); + return newval; + } + /// Get the name of the Validator + const std::string &get_name() const { return name_; } + /// Specify whether the Validator is active or not + Validator &active(bool active_val = true) { + active_ = active_val; + return *this; + } + /// Specify whether the Validator is active or not + Validator active(bool active_val = true) const { + Validator newval(*this); + newval.active_ = active_val; + return newval; + } + + /// Specify whether the Validator can be modifying or not + Validator &non_modifying(bool no_modify = true) { + non_modifying_ = no_modify; + return *this; + } + /// Specify the application index of a validator + Validator &application_index(int app_index) { + application_index_ = app_index; + return *this; + } + /// Specify the application index of a validator + Validator application_index(int app_index) const { + Validator newval(*this); + newval.application_index_ = app_index; + return newval; + } + /// Get the current value of the application index + int get_application_index() const { return application_index_; } + /// Get a boolean if the validator is active + bool get_active() const { return active_; } + + /// Get a boolean if the validator is allowed to modify the input returns true if it can modify the input + bool get_modifying() const { return !non_modifying_; } + + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the + /// same. + Validator operator&(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " AND "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(!s1.empty() && !s2.empty()) + return std::string("(") + s1 + ") AND (" + s2 + ")"; + else + return s1 + s2; + }; + + newval.active_ = (active_ & other.active_); + newval.application_index_ = application_index_; + return newval; + } + + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the + /// same. + Validator operator|(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " OR "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(s1.empty() || s2.empty()) + return std::string(); + + return std::string("(") + s1 + ") OR (" + s2 + ")"; + }; + newval.active_ = (active_ & other.active_); + newval.application_index_ = application_index_; + return newval; + } + + /// Create a validator that fails when a given validator succeeds + Validator operator!() const { + Validator newval; + const std::function &dfunc1 = desc_function_; + newval.desc_function_ = [dfunc1]() { + auto str = dfunc1(); + return (!str.empty()) ? std::string("NOT ") + str : std::string{}; + }; + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + + newval.func_ = [f1, dfunc1](std::string &test) -> std::string { + std::string s1 = f1(test); + if(s1.empty()) { + return std::string("check ") + dfunc1() + " succeeded improperly"; + } + return std::string{}; + }; + newval.active_ = active_; + newval.application_index_ = application_index_; + return newval; + } + + private: + void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger) { + + const std::function &dfunc1 = val1.desc_function_; + const std::function &dfunc2 = val2.desc_function_; + + desc_function_ = [=]() { + std::string f1 = dfunc1(); + std::string f2 = dfunc2(); + if((f1.empty()) || (f2.empty())) { + return f1 + f2; + } + return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')'; + }; + } +}; // namespace CLI + +/// Class wrapping some of the accessors of Validator +class CustomValidator : public Validator { + public: +}; +// The implementation of the built in validators is using the Validator class; +// the user is only expected to use the const (static) versions (since there's no setup). +// Therefore, this is in detail. +namespace detail { + +/// CLI enumeration of different file types +enum class path_type { nonexistent, file, directory }; + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +/// get the type of the path from a file name +inline path_type check_path(const char *file) noexcept { + std::error_code ec; + auto stat = std::filesystem::status(file, ec); + if(ec) { + return path_type::nonexistent; + } + switch(stat.type()) { + case std::filesystem::file_type::none: + case std::filesystem::file_type::not_found: + return path_type::nonexistent; + case std::filesystem::file_type::directory: + return path_type::directory; + case std::filesystem::file_type::symlink: + case std::filesystem::file_type::block: + case std::filesystem::file_type::character: + case std::filesystem::file_type::fifo: + case std::filesystem::file_type::socket: + case std::filesystem::file_type::regular: + case std::filesystem::file_type::unknown: + default: + return path_type::file; + } +} +#else +/// get the type of the path from a file name +inline path_type check_path(const char *file) noexcept { +#if defined(_MSC_VER) + struct __stat64 buffer; + if(_stat64(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#else + struct stat buffer; + if(stat(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#endif + return path_type::nonexistent; +} +#endif +/// Check for an existing file (returns error message if check fails) +class ExistingFileValidator : public Validator { + public: + ExistingFileValidator() : Validator("FILE") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "File does not exist: " + filename; + } + if(path_result == path_type::directory) { + return "File is actually a directory: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an existing directory (returns error message if check fails) +class ExistingDirectoryValidator : public Validator { + public: + ExistingDirectoryValidator() : Validator("DIR") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Directory does not exist: " + filename; + } + if(path_result == path_type::file) { + return "Directory is actually a file: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an existing path +class ExistingPathValidator : public Validator { + public: + ExistingPathValidator() : Validator("PATH(existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Path does not exist: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an non-existing path +class NonexistentPathValidator : public Validator { + public: + NonexistentPathValidator() : Validator("PATH(non-existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result != path_type::nonexistent) { + return "Path already exists: " + filename; + } + return std::string(); + }; + } +}; + +/// Validate the given string is a legal ipv4 address +class IPV4Validator : public Validator { + public: + IPV4Validator() : Validator("IPV4") { + func_ = [](std::string &ip_addr) { + auto result = CLI::detail::split(ip_addr, '.'); + if(result.size() != 4) { + return std::string("Invalid IPV4 address must have four parts (") + ip_addr + ')'; + } + int num; + for(const auto &var : result) { + bool retval = detail::lexical_cast(var, num); + if(!retval) { + return std::string("Failed parsing number (") + var + ')'; + } + if(num < 0 || num > 255) { + return std::string("Each IP number must be between 0 and 255 ") + var; + } + } + return std::string(); + }; + } +}; + +/// Validate the argument is a number and greater than 0 +class PositiveNumber : public Validator { + public: + PositiveNumber() : Validator("POSITIVE") { + func_ = [](std::string &number_str) { + double number; + if(!detail::lexical_cast(number_str, number)) { + return std::string("Failed parsing number: (") + number_str + ')'; + } + if(number <= 0) { + return std::string("Number less or equal to 0: (") + number_str + ')'; + } + return std::string(); + }; + } +}; +/// Validate the argument is a number and greater than or equal to 0 +class NonNegativeNumber : public Validator { + public: + NonNegativeNumber() : Validator("NONNEGATIVE") { + func_ = [](std::string &number_str) { + double number; + if(!detail::lexical_cast(number_str, number)) { + return std::string("Failed parsing number: (") + number_str + ')'; + } + if(number < 0) { + return std::string("Number less than 0: (") + number_str + ')'; + } + return std::string(); + }; + } +}; + +/// Validate the argument is a number +class Number : public Validator { + public: + Number() : Validator("NUMBER") { + func_ = [](std::string &number_str) { + double number; + if(!detail::lexical_cast(number_str, number)) { + return std::string("Failed parsing as a number (") + number_str + ')'; + } + return std::string(); + }; + } +}; + +} // namespace detail + +// Static is not needed here, because global const implies static. + +/// Check for existing file (returns error message if check fails) +const detail::ExistingFileValidator ExistingFile; + +/// Check for an existing directory (returns error message if check fails) +const detail::ExistingDirectoryValidator ExistingDirectory; + +/// Check for an existing path +const detail::ExistingPathValidator ExistingPath; + +/// Check for an non-existing path +const detail::NonexistentPathValidator NonexistentPath; + +/// Check for an IP4 address +const detail::IPV4Validator ValidIPV4; + +/// Check for a positive number +const detail::PositiveNumber PositiveNumber; + +/// Check for a non-negative number +const detail::NonNegativeNumber NonNegativeNumber; + +/// Check for a number +const detail::Number Number; + +/// Produce a range (factory). Min and max are inclusive. +class Range : public Validator { + public: + /// This produces a range with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template Range(T min, T max) { + std::stringstream out; + out << detail::type_name() << " in [" << min << " - " << max << "]"; + description(out.str()); + + func_ = [min, max](std::string &input) { + T val; + bool converted = detail::lexical_cast(input, val); + if((!converted) || (val < min || val > max)) + return std::string("Value ") + input + " not in range " + std::to_string(min) + " to " + + std::to_string(max); + + return std::string(); + }; + } + + /// Range of one value is 0 to value + template explicit Range(T max) : Range(static_cast(0), max) {} +}; + +/// Produce a bounded range (factory). Min and max are inclusive. +class Bound : public Validator { + public: + /// This bounds a value with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template Bound(T min, T max) { + std::stringstream out; + out << detail::type_name() << " bounded to [" << min << " - " << max << "]"; + description(out.str()); + + func_ = [min, max](std::string &input) { + T val; + bool converted = detail::lexical_cast(input, val); + if(!converted) { + return std::string("Value ") + input + " could not be converted"; + } + if(val < min) + input = detail::to_string(min); + else if(val > max) + input = detail::to_string(max); + + return std::string{}; + }; + } + + /// Range of one value is 0 to value + template explicit Bound(T max) : Bound(static_cast(0), max) {} +}; + +namespace detail { +template ::type>::value, detail::enabler> = detail::dummy> +auto smart_deref(T value) -> decltype(*value) { + return *value; +} + +template < + typename T, + enable_if_t::type>::value, detail::enabler> = detail::dummy> +typename std::remove_reference::type &smart_deref(T &value) { + return value; +} +/// Generate a string representation of a set +template std::string generate_set(const T &set) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(set), + [](const iteration_type_t &v) { return detail::pair_adaptor::first(v); }, + ",")); + out.push_back('}'); + return out; +} + +/// Generate a string representation of a map +template std::string generate_map(const T &map, bool key_only = false) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(map), + [key_only](const iteration_type_t &v) { + std::string res{detail::to_string(detail::pair_adaptor::first(v))}; + + if(!key_only) { + res.append("->"); + res += detail::to_string(detail::pair_adaptor::second(v)); + } + return res; + }, + ",")); + out.push_back('}'); + return out; +} + +template struct has_find { + template + static auto test(int) -> decltype(std::declval().find(std::declval()), std::true_type()); + template static auto test(...) -> decltype(std::false_type()); + + static const auto value = decltype(test(0))::value; + using type = std::integral_constant; +}; + +/// A search function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + using element_t = typename detail::element_type::type; + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) { + return (detail::pair_adaptor::first(v) == val); + }); + return {(it != std::end(setref)), it}; +} + +/// A search function that uses the built in find function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + auto &setref = detail::smart_deref(set); + auto it = setref.find(val); + return {(it != std::end(setref)), it}; +} + +/// A search function with a filter function +template +auto search(const T &set, const V &val, const std::function &filter_function) + -> std::pair { + using element_t = typename detail::element_type::type; + // do the potentially faster first search + auto res = search(set, val); + if((res.first) || (!(filter_function))) { + return res; + } + // if we haven't found it do the longer linear search with all the element translations + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) { + V a{detail::pair_adaptor::first(v)}; + a = filter_function(a); + return (a == val); + }); + return {(it != std::end(setref)), it}; +} + +// the following suggestion was made by Nikita Ofitserov(@himikof) +// done in templates to prevent compiler warnings on negation of unsigned numbers + +/// Do a check for overflow on signed numbers +template +inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { + if((a > 0) == (b > 0)) { + return ((std::numeric_limits::max)() / (std::abs)(a) < (std::abs)(b)); + } else { + return ((std::numeric_limits::min)() / (std::abs)(a) > -(std::abs)(b)); + } +} +/// Do a check for overflow on unsigned numbers +template +inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { + return ((std::numeric_limits::max)() / a < b); +} + +/// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise. +template typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { + if(a == 0 || b == 0 || a == 1 || b == 1) { + a *= b; + return true; + } + if(a == (std::numeric_limits::min)() || b == (std::numeric_limits::min)()) { + return false; + } + if(overflowCheck(a, b)) { + return false; + } + a *= b; + return true; +} + +/// Performs a *= b; if it doesn't equal infinity. Returns false otherwise. +template +typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { + T c = a * b; + if(std::isinf(c) && !std::isinf(a) && !std::isinf(b)) { + return false; + } + a = c; + return true; +} + +} // namespace detail +/// Verify items are in a set +class IsMember : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction using an initializer list + template + IsMember(std::initializer_list values, Args &&... args) + : IsMember(std::vector(values), std::forward(args)...) {} + + /// This checks to see if an item is in a set (empty function) + template explicit IsMember(T &&set) : IsMember(std::forward(set), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit IsMember(T set, F filter_function) { + + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + + using local_item_t = typename IsMemberType::type; // This will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); }; + + // This is the function that validates + // It stores a copy of the set pointer-like, so shared_ptr will stay alive + func_ = [set, filter_fn](std::string &input) { + local_item_t b; + if(!detail::lexical_cast(input, b)) { + throw ValidationError(input); // name is added later + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(set, b, filter_fn); + if(res.first) { + // Make sure the version in the input string is identical to the one in the set + if(filter_fn) { + input = detail::value_string(detail::pair_adaptor::first(*(res.second))); + } + + // Return empty error string (success) + return std::string{}; + } + + // If you reach this point, the result was not found + std::string out(" not in "); + out += detail::generate_set(detail::smart_deref(set)); + return out; + }; + } + + /// You can pass in as many filter functions as you like, they nest (string only currently) + template + IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) + : IsMember( + std::forward(set), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// definition of the default transformation object +template using TransformPairs = std::vector>; + +/// Translate named items to other or a value set +class Transformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + Transformer(std::initializer_list> values, Args &&... args) + : Transformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit Transformer(T &&mapping) : Transformer(std::forward(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit Transformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; + + func_ = [mapping, filter_fn](std::string &input) { + local_item_t b; + if(!detail::lexical_cast(input, b)) { + return std::string(); + // there is no possible way we can match anything in the mapping if we can't convert so just return + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + } + return std::string{}; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) + : Transformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// translate named items to other or a value set +class CheckedTransformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + CheckedTransformer(std::initializer_list> values, Args &&... args) + : CheckedTransformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit CheckedTransformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + auto tfunc = [mapping]() { + std::string out("value in "); + out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; + out += detail::join( + detail::smart_deref(mapping), + [](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor::second(v)); }, + ","); + out.push_back('}'); + return out; + }; + + desc_function_ = tfunc; + + func_ = [mapping, tfunc, filter_fn](std::string &input) { + local_item_t b; + bool converted = detail::lexical_cast(input, b); + if(converted) { + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + return std::string{}; + } + } + for(const auto &v : detail::smart_deref(mapping)) { + auto output_string = detail::value_string(detail::pair_adaptor::second(v)); + if(output_string == input) { + return std::string(); + } + } + + return "Check " + input + " " + tfunc() + " FAILED"; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) + : CheckedTransformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// Helper function to allow ignore_case to be passed to IsMember or Transform +inline std::string ignore_case(std::string item) { return detail::to_lower(item); } + +/// Helper function to allow ignore_underscore to be passed to IsMember or Transform +inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); } + +/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform +inline std::string ignore_space(std::string item) { + item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item)); + item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item)); + return item; +} + +/// Multiply a number by a factor using given mapping. +/// Can be used to write transforms for SIZE or DURATION inputs. +/// +/// Example: +/// With mapping = `{"b"->1, "kb"->1024, "mb"->1024*1024}` +/// one can recognize inputs like "100", "12kb", "100 MB", +/// that will be automatically transformed to 100, 14448, 104857600. +/// +/// Output number type matches the type in the provided mapping. +/// Therefore, if it is required to interpret real inputs like "0.42 s", +/// the mapping should be of a type or . +class AsNumberWithUnit : public Validator { + public: + /// Adjust AsNumberWithUnit behavior. + /// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched. + /// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError + /// if UNIT_REQUIRED is set and unit literal is not found. + enum Options { + CASE_SENSITIVE = 0, + CASE_INSENSITIVE = 1, + UNIT_OPTIONAL = 0, + UNIT_REQUIRED = 2, + DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL + }; + + template + explicit AsNumberWithUnit(std::map mapping, + Options opts = DEFAULT, + const std::string &unit_name = "UNIT") { + description(generate_description(unit_name, opts)); + validate_mapping(mapping, opts); + + // transform function + func_ = [mapping, opts](std::string &input) -> std::string { + Number num; + + detail::rtrim(input); + if(input.empty()) { + throw ValidationError("Input is empty"); + } + + // Find split position between number and prefix + auto unit_begin = input.end(); + while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) { + --unit_begin; + } + + std::string unit{unit_begin, input.end()}; + input.resize(static_cast(std::distance(input.begin(), unit_begin))); + detail::trim(input); + + if(opts & UNIT_REQUIRED && unit.empty()) { + throw ValidationError("Missing mandatory unit"); + } + if(opts & CASE_INSENSITIVE) { + unit = detail::to_lower(unit); + } + + bool converted = detail::lexical_cast(input, num); + if(!converted) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + + if(unit.empty()) { + // No need to modify input if no unit passed + return {}; + } + + // find corresponding factor + auto it = mapping.find(unit); + if(it == mapping.end()) { + throw ValidationError(unit + + " unit not recognized. " + "Allowed values: " + + detail::generate_map(mapping, true)); + } + + // perform safe multiplication + bool ok = detail::checked_multiply(num, it->second); + if(!ok) { + throw ValidationError(detail::to_string(num) + " multiplied by " + unit + + " factor would cause number overflow. Use smaller value."); + } + input = detail::to_string(num); + + return {}; + }; + } + + private: + /// Check that mapping contains valid units. + /// Update mapping for CASE_INSENSITIVE mode. + template static void validate_mapping(std::map &mapping, Options opts) { + for(auto &kv : mapping) { + if(kv.first.empty()) { + throw ValidationError("Unit must not be empty."); + } + if(!detail::isalpha(kv.first)) { + throw ValidationError("Unit must contain only letters."); + } + } + + // make all units lowercase if CASE_INSENSITIVE + if(opts & CASE_INSENSITIVE) { + std::map lower_mapping; + for(auto &kv : mapping) { + auto s = detail::to_lower(kv.first); + if(lower_mapping.count(s)) { + throw ValidationError(std::string("Several matching lowercase unit representations are found: ") + + s); + } + lower_mapping[detail::to_lower(kv.first)] = kv.second; + } + mapping = std::move(lower_mapping); + } + } + + /// Generate description like this: NUMBER [UNIT] + template static std::string generate_description(const std::string &name, Options opts) { + std::stringstream out; + out << detail::type_name() << ' '; + if(opts & UNIT_REQUIRED) { + out << name; + } else { + out << '[' << name << ']'; + } + return out.str(); + } +}; + +/// Converts a human-readable size string (with unit literal) to uin64_t size. +/// Example: +/// "100" => 100 +/// "1 b" => 100 +/// "10Kb" => 10240 // you can configure this to be interpreted as kilobyte (*1000) or kibibyte (*1024) +/// "10 KB" => 10240 +/// "10 kb" => 10240 +/// "10 kib" => 10240 // *i, *ib are always interpreted as *bibyte (*1024) +/// "10kb" => 10240 +/// "2 MB" => 2097152 +/// "2 EiB" => 2^61 // Units up to exibyte are supported +class AsSizeValue : public AsNumberWithUnit { + public: + using result_t = std::uint64_t; + + /// If kb_is_1000 is true, + /// interpret 'kb', 'k' as 1000 and 'kib', 'ki' as 1024 + /// (same applies to higher order units as well). + /// Otherwise, interpret all literals as factors of 1024. + /// The first option is formally correct, but + /// the second interpretation is more wide-spread + /// (see https://en.wikipedia.org/wiki/Binary_prefix). + explicit AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) { + if(kb_is_1000) { + description("SIZE [b, kb(=1000b), kib(=1024b), ...]"); + } else { + description("SIZE [b, kb(=1024b), ...]"); + } + } + + private: + /// Get mapping + static std::map init_mapping(bool kb_is_1000) { + std::map m; + result_t k_factor = kb_is_1000 ? 1000 : 1024; + result_t ki_factor = 1024; + result_t k = 1; + result_t ki = 1; + m["b"] = 1; + for(std::string p : {"k", "m", "g", "t", "p", "e"}) { + k *= k_factor; + ki *= ki_factor; + m[p] = k; + m[p + "b"] = k; + m[p + "i"] = ki; + m[p + "ib"] = ki; + } + return m; + } + + /// Cache calculated mapping + static std::map get_mapping(bool kb_is_1000) { + if(kb_is_1000) { + static auto m = init_mapping(true); + return m; + } else { + static auto m = init_mapping(false); + return m; + } + } +}; + +namespace detail { +/// Split a string into a program name and command line arguments +/// the string is assumed to contain a file name followed by other arguments +/// the return value contains is a pair with the first argument containing the program name and the second +/// everything else. +inline std::pair split_program_name(std::string commandline) { + // try to determine the programName + std::pair vals; + trim(commandline); + auto esp = commandline.find_first_of(' ', 1); + while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) { + esp = commandline.find_first_of(' ', esp + 1); + if(esp == std::string::npos) { + // if we have reached the end and haven't found a valid file just assume the first argument is the + // program name + esp = commandline.find_first_of(' ', 1); + break; + } + } + vals.first = commandline.substr(0, esp); + rtrim(vals.first); + // strip the program name + vals.second = (esp != std::string::npos) ? commandline.substr(esp + 1) : std::string{}; + ltrim(vals.second); + return vals; +} + +} // namespace detail +/// @} + +} // namespace CLI + +// From FormatterFwd.hpp: + +namespace CLI { + +class Option; +class App; + +/// This enum signifies the type of help requested +/// +/// This is passed in by App; all user classes must accept this as +/// the second argument. + +enum class AppFormatMode { + Normal, ///< The normal, detailed help + All, ///< A fully expanded help + Sub, ///< Used when printed as part of expanded subcommand +}; + +/// This is the minimum requirements to run a formatter. +/// +/// A user can subclass this is if they do not care at all +/// about the structure in CLI::Formatter. +class FormatterBase { + protected: + /// @name Options + ///@{ + + /// The width of the first column + std::size_t column_width_{30}; + + /// @brief The required help printout labels (user changeable) + /// Values are Needs, Excludes, etc. + std::map labels_{}; + + ///@} + /// @name Basic + ///@{ + + public: + FormatterBase() = default; + FormatterBase(const FormatterBase &) = default; + FormatterBase(FormatterBase &&) = default; + + /// Adding a destructor in this form to work around bug in GCC 4.7 + virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default) + + /// This is the key method that puts together help + virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0; + + ///@} + /// @name Setters + ///@{ + + /// Set the "REQUIRED" label + void label(std::string key, std::string val) { labels_[key] = val; } + + /// Set the column width + void column_width(std::size_t val) { column_width_ = val; } + + ///@} + /// @name Getters + ///@{ + + /// Get the current value of a name (REQUIRED, etc.) + std::string get_label(std::string key) const { + if(labels_.find(key) == labels_.end()) + return key; + else + return labels_.at(key); + } + + /// Get the current column width + std::size_t get_column_width() const { return column_width_; } + + ///@} +}; + +/// This is a specialty override for lambda functions +class FormatterLambda final : public FormatterBase { + using funct_t = std::function; + + /// The lambda to hold and run + funct_t lambda_; + + public: + /// Create a FormatterLambda with a lambda function + explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {} + + /// Adding a destructor (mostly to make GCC 4.7 happy) + ~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default) + + /// This will simply call the lambda function + std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { + return lambda_(app, name, mode); + } +}; + +/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few +/// overridable methods, to be highly customizable with minimal effort. +class Formatter : public FormatterBase { + public: + Formatter() = default; + Formatter(const Formatter &) = default; + Formatter(Formatter &&) = default; + + /// @name Overridables + ///@{ + + /// This prints out a group of options with title + /// + virtual std::string make_group(std::string group, bool is_positional, std::vector opts) const; + + /// This prints out just the positionals "group" + virtual std::string make_positionals(const App *app) const; + + /// This prints out all the groups of options + std::string make_groups(const App *app, AppFormatMode mode) const; + + /// This prints out all the subcommands + virtual std::string make_subcommands(const App *app, AppFormatMode mode) const; + + /// This prints out a subcommand + virtual std::string make_subcommand(const App *sub) const; + + /// This prints out a subcommand in help-all + virtual std::string make_expanded(const App *sub) const; + + /// This prints out all the groups of options + virtual std::string make_footer(const App *app) const; + + /// This displays the description line + virtual std::string make_description(const App *app) const; + + /// This displays the usage line + virtual std::string make_usage(const App *app, std::string name) const; + + /// This puts everything together + std::string make_help(const App * /*app*/, std::string, AppFormatMode) const override; + + ///@} + /// @name Options + ///@{ + + /// This prints out an option help line, either positional or optional form + virtual std::string make_option(const Option *opt, bool is_positional) const { + std::stringstream out; + detail::format_help( + out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_); + return out.str(); + } + + /// @brief This is the name part of an option, Default: left column + virtual std::string make_option_name(const Option *, bool) const; + + /// @brief This is the options part of the name, Default: combined into left column + virtual std::string make_option_opts(const Option *) const; + + /// @brief This is the description. Default: Right column, on new line if left column too large + virtual std::string make_option_desc(const Option *) const; + + /// @brief This is used to print the name on the USAGE line + virtual std::string make_option_usage(const Option *opt) const; + + ///@} +}; + +} // namespace CLI + +// From Option.hpp: + +namespace CLI { + +using results_t = std::vector; +/// callback function definition +using callback_t = std::function; + +class Option; +class App; + +using Option_p = std::unique_ptr