├── .github ├── CODEOWNERS └── workflows │ └── main.yml ├── src ├── TestApp │ ├── paket.references │ ├── App.config │ ├── BlackFox.MasterOfFoo.TestApp.fsproj │ ├── AssemblyInfo.fs │ └── Program.fs ├── BlackFox.MasterOfFoo.Tests │ ├── paket.references │ ├── Main.fs │ ├── BlackFox.MasterOfFoo.Tests.fsproj │ ├── Test.fs │ └── InternalRepresentationTests.fs ├── BlackFox.MasterOfFoo │ ├── Icon.png │ ├── Icon.afdesign │ ├── Core │ │ └── Readme.md │ ├── sformat.fs │ ├── BlackFox.MasterOfFoo.fsproj │ ├── MasterOfFoo.fs │ ├── FormatSpecification.fs │ ├── PrintableElement.fs │ ├── PrintfEnv.fs │ └── printf.fs ├── BlackFox.MasterOfFoo.Build │ ├── Program.fs │ ├── paket.references │ ├── BlackFox.MasterOfFoo.Build.fsproj │ └── Tasks.fs ├── Directory.Build.props └── MasterOfFoo.sln ├── paket.cmd ├── paket.sh ├── global.json ├── .vscode ├── extensions.json └── settings.json ├── .config └── dotnet-tools.json ├── .editorconfig ├── paket.dependencies ├── Release Notes.md ├── .gitattributes ├── .gitignore ├── Readme.md ├── LICENSE ├── paket.lock └── .paket └── Paket.Restore.targets /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @vbfox 2 | -------------------------------------------------------------------------------- /src/TestApp/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Core 2 | System.Data.SqlClient 3 | -------------------------------------------------------------------------------- /paket.cmd: -------------------------------------------------------------------------------- 1 | @dotnet tool restore --verbosity quiet 2 | 3 | dotnet paket %* 4 | -------------------------------------------------------------------------------- /src/BlackFox.MasterOfFoo.Tests/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Core 2 | Expecto 3 | -------------------------------------------------------------------------------- /paket.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dotnet tool restore --verbosity minimal 4 | dotnet paket $@ 5 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.201", 4 | "rollForward": "minor" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/BlackFox.MasterOfFoo/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbfox/MasterOfFoo/HEAD/src/BlackFox.MasterOfFoo/Icon.png -------------------------------------------------------------------------------- /src/BlackFox.MasterOfFoo/Icon.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbfox/MasterOfFoo/HEAD/src/BlackFox.MasterOfFoo/Icon.afdesign -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "Ionide.Ionide-fsharp", 4 | "Ionide.Ionide-FAKE", 5 | "Ionide.Ionide-Paket", 6 | "EditorConfig.EditorConfig" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/TestApp/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "paket": { 6 | "version": "8.0.3", 7 | "commands": [ 8 | "paket" 9 | ] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/BlackFox.MasterOfFoo.Build/Program.fs: -------------------------------------------------------------------------------- 1 | module BlackFox.MasterOfFoo.Build.Program 2 | 3 | open BlackFox.Fake 4 | open Fake.Core 5 | open Fake.BuildServer 6 | 7 | [] 8 | let main argv = 9 | BuildTask.setupContextFromArgv argv 10 | BuildServer.install [ GitHubActions.Installer ] 11 | 12 | let defaultTask = Tasks.createAndGetDefault () 13 | BuildTask.runOrDefaultApp defaultTask 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | insert_final_newline = true 7 | end_of_line = lf 8 | 9 | # Xml project files 10 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 11 | indent_size = 2 12 | 13 | # Xml config files 14 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] 15 | indent_size = 2 16 | 17 | [*.{yml,yaml}] 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /src/BlackFox.MasterOfFoo.Build/paket.references: -------------------------------------------------------------------------------- 1 | group build 2 | FSharp.Core 3 | BlackFox.Fake.BuildTask 4 | 5 | Fake.Core.Environment 6 | Fake.Core.Process 7 | Fake.Core.Trace 8 | Fake.Core.Target 9 | Fake.Core.ReleaseNotes 10 | Fake.Core.UserInput 11 | Fake.IO.FileSystem 12 | Fake.IO.Zip 13 | Fake.Tools.Git 14 | Fake.DotNet.Cli 15 | Fake.DotNet.AssemblyInfoFile 16 | Fake.DotNet.Testing.Expecto 17 | Fake.DotNet.Paket 18 | Fake.BuildServer.GitHubActions 19 | Fake.Api.GitHub 20 | -------------------------------------------------------------------------------- /src/BlackFox.MasterOfFoo.Build/BlackFox.MasterOfFoo.Build.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | false 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/BlackFox.MasterOfFoo.Tests/Main.fs: -------------------------------------------------------------------------------- 1 | module Main 2 | 3 | open System.Threading 4 | open System.Globalization 5 | 6 | open Expecto 7 | 8 | [] 9 | let main args = 10 | // Some tests use the .Net formating formats like $"{System.Math.PI:N3}" and their representation 11 | // depends on the culture 12 | Thread.CurrentThread.CurrentCulture <- CultureInfo.InvariantCulture 13 | 14 | let writeResults = TestResults.writeNUnitSummary "TestResults.xml" 15 | let config = defaultConfig.appendSummaryHandler writeResults 16 | runTestsInAssembly config args 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | // Configure glob patterns for excluding files and folders. 4 | "files.exclude": { 5 | "**/.git": true, 6 | "**/.svn": true, 7 | "**/.hg": true, 8 | "**/.DS_Store": true, 9 | "**/.fake": true, 10 | "**/.paket": true, 11 | "**/.vs": true 12 | }, 13 | "search.exclude": { 14 | "**/node_modules": true, 15 | "**/bower_components": true, 16 | "**/packages": true, 17 | "**/paket-files": true, 18 | "**/artifacts": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/BlackFox.MasterOfFoo.Tests/BlackFox.MasterOfFoo.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0;net5.0;netcoreapp2.0 4 | Exe 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/TestApp/BlackFox.MasterOfFoo.TestApp.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Exe 6 | false 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/BlackFox.MasterOfFoo/Core/Readme.md: -------------------------------------------------------------------------------- 1 | # Core 2 | 3 | Core is the part of MasterOfFoo that is extracted from the F# Compiler. 4 | 5 | # Extraction Process 6 | 7 | * Copy `printf.fs` from the compiler and fix namespace 8 | * Copy `FormatOptions` into `sformat.fs` and all new functions necessary. Keep `anyToStringForPrintf`. 9 | * Extract `PrintfEnv` into `PrintEnv.fs` 10 | * Move `FormatSpecifier` and `FormatFlags` to `FormatSpecification.fs` 11 | * Replace `PrintfEnv` signature with `abstract Write : PrintableElement -> unit` & fix thigns 12 | * `findNextFormatSpecifier` returns `PrintableElement` 13 | * The cornerstone is that ValueConverter should now return a function generating PrintableElement instances 14 | instead of strings. 15 | 16 | There are quite a few things to fix but the original `printf.fs` and `sformat.fs` are commited to serve as guide by 17 | diffing the current code and new versions before upgrade. 18 | -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://api.nuget.org/v3/index.json 2 | framework: net8.0,net5.0,netcoreapp2.0,netstandard2.0,net461 3 | storage:none 4 | 5 | nuget Expecto ~> 9 6 | nuget FSharp.Core 7 | nuget System.Data.SqlClient 8 | 9 | // Build infrastructure 10 | group build 11 | source https://api.nuget.org/v3/index.json 12 | storage: none 13 | framework: net8.0 14 | 15 | nuget FSharp.Core ~> 5 16 | nuget BlackFox.Fake.BuildTask 17 | 18 | nuget Octokit 0.48 // Fake.Api.GitHub 5.20.4 references 0.48+ but 0.50 has some incompatible Api changes 19 | nuget Fake.Core.Target 20 | nuget Fake.Core.Environment 21 | nuget Fake.Core.Process 22 | nuget Fake.Core.Trace 23 | nuget Fake.Core.ReleaseNotes 24 | nuget Fake.Core.UserInput 25 | nuget Fake.IO.FileSystem 26 | nuget Fake.IO.Zip 27 | nuget Fake.Tools.Git 28 | nuget Fake.DotNet.Cli 29 | nuget Fake.DotNet.AssemblyInfoFile 30 | nuget Fake.DotNet.Testing.Expecto 31 | nuget Fake.DotNet.Paket 32 | nuget Fake.BuildServer.GitHubActions 33 | nuget Fake.Api.GitHub 34 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)/../artifacts/')) 4 | 5 | 6 | $([System.IO.Path]::GetFullPath('$(ArtifactsDir)/$(MSBuildProjectName)/$(Configuration)/')) 7 | 8 | 9 | An F# library to allow using printf style strings in more places. 10 | https://github.com/vbfox/MasterOfFoo 11 | Apache-2.0 12 | https://github.com/vbfox/MasterOfFoo.git 13 | F#;FSharp;printf 14 | vbfox 15 | 16 | 17 | true 18 | $(NoWarn);FS2003;NU1902;NU1903;NU1904;NETSDK1138 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/BlackFox.MasterOfFoo/sformat.fs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. 2 | 3 | namespace BlackFox.MasterOfFoo.Core 4 | 5 | open System 6 | open System.Reflection 7 | open System.Globalization 8 | 9 | /// These are a typical set of options used to control structured formatting. 10 | [] 11 | type internal FormatOptions = 12 | { FloatingPointFormat: string 13 | AttributeProcessor: (string -> (string * string) list -> bool -> unit) 14 | FormatProvider: IFormatProvider 15 | BindingFlags: BindingFlags 16 | PrintWidth: int 17 | PrintDepth: int 18 | PrintLength: int 19 | PrintSize: int 20 | ShowProperties: bool 21 | ShowIEnumerable: bool 22 | } 23 | 24 | static member Default = 25 | { FormatProvider = (CultureInfo.InvariantCulture :> IFormatProvider) 26 | AttributeProcessor= (fun _ _ _ -> ()) 27 | BindingFlags = BindingFlags.Public 28 | FloatingPointFormat = "g10" 29 | PrintWidth = 80 30 | PrintDepth = 100 31 | PrintLength = 100 32 | PrintSize = 10000 33 | ShowProperties = false 34 | ShowIEnumerable = true 35 | } 36 | 37 | module internal Display = 38 | let anyToStringForPrintf (_options: FormatOptions) (_bindingFlags:BindingFlags) (value, _typValue: Type): string = 39 | sprintf "%A" value 40 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | linux: 11 | runs-on: ubuntu-22.04 12 | env: 13 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 14 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 15 | DOTNET_NOLOGO: 1 16 | PAKET_SKIP_RESTORE_TARGETS: true 17 | steps: 18 | - uses: actions/checkout@v6 19 | - uses: actions/setup-dotnet@v5 20 | with: 21 | dotnet-version: | 22 | 2 23 | 5 24 | - name: Restore packages 25 | run: ./paket.sh restore 26 | - name: Compile build script 27 | run: dotnet build src/BlackFox.MasterOfFoo.Build/BlackFox.MasterOfFoo.Build.fsproj 28 | - name: Build 29 | run: ./build.sh CI 30 | - name: Publish artifacts 31 | uses: actions/upload-artifact@v6 32 | with: 33 | path: artifacts/BlackFox.MasterOfFoo/Release/*.nupkg 34 | if-no-files-found: error 35 | compression-level: 0 36 | 37 | windows: 38 | runs-on: windows-latest 39 | env: 40 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 41 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 42 | DOTNET_NOLOGO: 1 43 | PAKET_SKIP_RESTORE_TARGETS: true 44 | steps: 45 | - uses: actions/checkout@v6 46 | - uses: actions/setup-dotnet@v5 47 | with: 48 | dotnet-version: | 49 | 2 50 | 5 51 | - name: Restore packages 52 | run: ./paket.cmd restore 53 | - name: Compile build script 54 | run: dotnet build src/BlackFox.MasterOfFoo.Build/BlackFox.MasterOfFoo.Build.fsproj 55 | - name: Build 56 | run: ./build.cmd CI 57 | -------------------------------------------------------------------------------- /src/TestApp/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace ConsoleApplication1.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /Release Notes.md: -------------------------------------------------------------------------------- 1 | ### New in 2.1.1 2 | 3 | * Fix FSharp.Core dependency not being specified in the NuGet package for `net5.0` target but the dll being built 4 | against 8.0.0 anyway. 5 | 6 | ### New in 2.1.0 7 | 8 | * Synchronize with the latest FSharp.Core version and support the `%B` format specifier 9 | 10 | ### New in 2.0.0 11 | 12 | * Include the readme in the NuGet package 13 | * Build with 8.0.201 SDK 14 | * The new set of supported platforms is .NET Framework 4.6.1, .NET Standard 2.0 and .NET 5.0 15 | * Add support for interpolated strings 16 | 17 | ### New in 1.0.6 18 | 19 | * Make dependency ranges less strict (For `FSharp.Core` 6.x) 20 | * Build with 5.0.201 SDK 21 | 22 | ### New in 1.0.5 23 | 24 | * Specify the package license using SPDX 25 | * Build with 2.1.500 SDK 26 | 27 | ### New in 1.0.4 28 | 29 | * Include pdb files in package (With SourceLink) 30 | * Include XmlDoc in package 31 | 32 | ### New in 1.0.3 33 | 34 | * Remove ValueTuple from references 35 | 36 | ### New in 1.0.2 37 | 38 | * Larger FSharp.Core version choice 39 | 40 | ### New in 1.0.1 41 | 42 | * Target .Net 4.5 instead 43 | 44 | ### New in 1.0.0 45 | 46 | * The library now targets .Net 4.0, .Net standard 1.6 and .Net standard 2.0 47 | 48 | ### New in 0.2.1 49 | 50 | * NuGet package was accidentally dependent on FSharpLint.MSBuild 51 | 52 | ### New in 0.2.0 53 | 54 | * Fix `%A` text generated by `FormatAsPrintF()`, it's now printing the same thing as `printf` / `sprintf`. 55 | 56 | ### New in 0.1.2 57 | 58 | * Fix xmldoc (Bad name in zip and absent in nuget) 59 | 60 | ### New in 0.1.1 61 | 62 | * NuGet package dependency bugfix 63 | 64 | ### New in 0.1.0 65 | 66 | * First version 67 | -------------------------------------------------------------------------------- /src/BlackFox.MasterOfFoo/BlackFox.MasterOfFoo.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0;net5.0 4 | net461;netstandard2.0;net5.0 5 | true 6 | true 7 | Icon.png 8 | true 9 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 10 | Readme.md 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/BlackFox.MasterOfFoo/MasterOfFoo.fs: -------------------------------------------------------------------------------- 1 | [] 2 | [] 3 | module BlackFox.MasterOfFoo.MasterOfFoo 4 | 5 | open System 6 | open System.Reflection 7 | open BlackFox.MasterOfFoo.Core 8 | open System.Collections.Concurrent 9 | 10 | module private FormatReflection = 11 | let capturesCache = ConcurrentDictionary() 12 | 13 | let inline getCaptures (format: Format<'Printer, 'State, 'Residue, 'Result>): obj[] = 14 | let genericType = format.GetType() 15 | let prop = capturesCache.GetOrAdd(genericType, fun t -> t.GetProperty("Captures")) 16 | if prop = null then 17 | null 18 | else 19 | prop.GetValue(format) :?> obj[] 20 | 21 | let captureTypesCache = ConcurrentDictionary() 22 | 23 | let inline getCaptureTypes (format: Format<'Printer, 'State, 'Residue, 'Result>): System.Type[] = 24 | let genericType = format.GetType() 25 | let prop = captureTypesCache.GetOrAdd(genericType, fun t -> t.GetProperty("CaptureTypes")) 26 | if prop = null then 27 | null 28 | else 29 | prop.GetValue(format) :?> Type[] 30 | 31 | open FormatReflection 32 | 33 | /// Take a format and a PrintfEnv builder to create a printf-like function 34 | let doPrintf (format: Format<'Printer, 'State, 'Residue, 'Result>) (envf: int -> #PrintfEnv<'State, 'Residue, 'Result>) = 35 | let cacheItem = Cache<_, _, _, _>.GetParser format 36 | 37 | match getCaptures format with 38 | | null -> 39 | // The ksprintf "...%d ...." arg path, producing a function 40 | let factory = cacheItem.GetCurriedPrinterFactory() 41 | let initial() = (envf cacheItem.BlockCount :> PrintfEnv<_,_,_>) 42 | factory.Invoke([], initial) 43 | | captures -> 44 | // The ksprintf $"...%d{3}...." path, running the steps straight away to produce a string 45 | let steps = cacheItem.GetStepsForCapturedFormat() 46 | let env = envf cacheItem.BlockCount :> PrintfEnv<_,_,_> 47 | let res = env.RunSteps(captures, getCaptureTypes format, steps) 48 | unbox res // prove 'T = 'Result 49 | //continuation res 50 | 51 | /// Take a format and a PrintfEnv to create a printf-like function 52 | let doPrintfFromEnv (format: Format<'Printer, 'State, 'Residue, 'Result>) (env: #PrintfEnv<_,_,_>) = 53 | doPrintf format (fun _ -> env) 54 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /src/MasterOfFoo.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 15 3 | VisualStudioVersion = 15.0.26403.7 4 | MinimumVisualStudioVersion = 15.0.26403.7 5 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "BlackFox.MasterOfFoo.TestApp", "TestApp\BlackFox.MasterOfFoo.TestApp.fsproj", "{4C628A7D-76EC-4673-A45D-DAE6FC060784}" 6 | EndProject 7 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E5E77D3C-F0EE-4EB3-B813-B181020563E0}" 8 | ProjectSection(SolutionItems) = preProject 9 | ..\paket.dependencies = ..\paket.dependencies 10 | ..\Readme.md = ..\Readme.md 11 | EndProjectSection 12 | EndProject 13 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "BlackFox.MasterOfFoo.Tests", "BlackFox.MasterOfFoo.Tests\BlackFox.MasterOfFoo.Tests.fsproj", "{B158E4B2-AF4C-4A91-BC1E-3D4E4B080802}" 14 | EndProject 15 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "BlackFox.MasterOfFoo", "BlackFox.MasterOfFoo\BlackFox.MasterOfFoo.fsproj", "{80E6D22C-3DEC-4929-B4BF-68E3A3F28039}" 16 | EndProject 17 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BlackFox.MasterOfFoo.Build", "BlackFox.MasterOfFoo.Build\BlackFox.MasterOfFoo.Build.fsproj", "{C1EEC1BE-433F-42C9-A2B2-B86AEB877608}" 18 | EndProject 19 | Global 20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 21 | Debug|Any CPU = Debug|Any CPU 22 | Release|Any CPU = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {4C628A7D-76EC-4673-A45D-DAE6FC060784}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {4C628A7D-76EC-4673-A45D-DAE6FC060784}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {4C628A7D-76EC-4673-A45D-DAE6FC060784}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {4C628A7D-76EC-4673-A45D-DAE6FC060784}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {B158E4B2-AF4C-4A91-BC1E-3D4E4B080802}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {B158E4B2-AF4C-4A91-BC1E-3D4E4B080802}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {B158E4B2-AF4C-4A91-BC1E-3D4E4B080802}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {B158E4B2-AF4C-4A91-BC1E-3D4E4B080802}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {80E6D22C-3DEC-4929-B4BF-68E3A3F28039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {80E6D22C-3DEC-4929-B4BF-68E3A3F28039}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {80E6D22C-3DEC-4929-B4BF-68E3A3F28039}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {80E6D22C-3DEC-4929-B4BF-68E3A3F28039}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {C1EEC1BE-433F-42C9-A2B2-B86AEB877608}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {C1EEC1BE-433F-42C9-A2B2-B86AEB877608}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {C1EEC1BE-433F-42C9-A2B2-B86AEB877608}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {C1EEC1BE-433F-42C9-A2B2-B86AEB877608}.Release|Any CPU.Build.0 = Release|Any CPU 41 | EndGlobalSection 42 | GlobalSection(SolutionProperties) = preSolution 43 | HideSolutionNode = FALSE 44 | EndGlobalSection 45 | GlobalSection(ExtensibilityGlobals) = postSolution 46 | SolutionGuid = {D7E9F6C5-DE27-456B-BAFD-3A445D56D74A} 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /src/BlackFox.MasterOfFoo/FormatSpecification.fs: -------------------------------------------------------------------------------- 1 | namespace BlackFox.MasterOfFoo 2 | 3 | open System 4 | 5 | [] 6 | type FormatFlags = 7 | | None = 0 8 | | LeftJustify = 1 9 | | PadWithZeros = 2 10 | | PlusForPositives = 4 11 | | SpaceForPositives = 8 12 | 13 | module internal FormatFlagsHelpers = 14 | let inline hasFlag flags (expected: FormatFlags) = (flags &&& expected) = expected 15 | let inline isLeftJustify flags = hasFlag flags FormatFlags.LeftJustify 16 | let inline isPadWithZeros flags = hasFlag flags FormatFlags.PadWithZeros 17 | let inline isPlusForPositives flags = hasFlag flags FormatFlags.PlusForPositives 18 | let inline isSpaceForPositives flags = hasFlag flags FormatFlags.SpaceForPositives 19 | 20 | open FormatFlagsHelpers 21 | 22 | module internal FormatSpecifierConstants = 23 | /// Used for width and precision to denote that user has specified '*' flag 24 | [] 25 | let StarValue = -1 26 | 27 | /// Used for width and precision to denote that corresponding value was omitted in format string 28 | [] 29 | let NotSpecifiedValue = -2 30 | 31 | open FormatSpecifierConstants 32 | 33 | [] 34 | [] 35 | type FormatSpecifier = 36 | { 37 | TypeChar: char 38 | Precision: int 39 | Width: int 40 | Flags: FormatFlags 41 | InteropHoleDotNetFormat: string voption 42 | } 43 | member spec.IsStarPrecision = (spec.Precision = StarValue) 44 | 45 | member spec.IsPrecisionSpecified = (spec.Precision <> NotSpecifiedValue) 46 | 47 | member spec.IsStarWidth = (spec.Width = StarValue) 48 | 49 | member spec.IsWidthSpecified = (spec.Width <> NotSpecifiedValue) 50 | 51 | member spec.ArgCount = 52 | let n = 53 | if spec.TypeChar = 'a' then 2 54 | elif spec.IsStarWidth || spec.IsStarPrecision then 55 | if spec.IsStarWidth = spec.IsStarPrecision then 3 56 | else 2 57 | else 1 58 | 59 | let n = if spec.TypeChar = '%' then n - 1 else n 60 | 61 | assert (n <> 0) 62 | 63 | n 64 | 65 | override spec.ToString() = 66 | let valueOf n = match n with StarValue -> "*" | NotSpecifiedValue -> "-" | n -> n.ToString() 67 | System.String.Format 68 | ( 69 | "'{0}', Precision={1}, Width={2}, Flags={3}", 70 | spec.TypeChar, 71 | (valueOf spec.Precision), 72 | (valueOf spec.Width), 73 | spec.Flags 74 | ) 75 | 76 | member spec.IsDecimalFormat = 77 | spec.TypeChar = 'M' 78 | 79 | member spec.GetPadAndPrefix allowZeroPadding = 80 | let padChar = if allowZeroPadding && isPadWithZeros spec.Flags then '0' else ' '; 81 | let prefix = 82 | if isPlusForPositives spec.Flags then "+" 83 | elif isSpaceForPositives spec.Flags then " " 84 | else "" 85 | padChar, prefix 86 | 87 | member spec.IsGFormat = 88 | spec.IsDecimalFormat || System.Char.ToLower(spec.TypeChar) = 'g' 89 | -------------------------------------------------------------------------------- /src/BlackFox.MasterOfFoo/PrintableElement.fs: -------------------------------------------------------------------------------- 1 | namespace BlackFox.MasterOfFoo 2 | 3 | open System 4 | open FormatSpecifierConstants 5 | 6 | /// The type of an element passed to a PrintfEnv. 7 | type PrintableElementType = 8 | /// A string created by the engine, only used in a few edge cases 9 | | MadeByEngine = 0uy 10 | /// A string coming directly from the format string 11 | | Direct = 1uy 12 | /// A format specifier and his corresponding value(s) 13 | | FromFormatSpecifier = 2uy 14 | 15 | /// An element passed to PrintfEnv for writing. 16 | type PrintableElement 17 | ( 18 | printer: unit -> string, 19 | value: obj, 20 | type': PrintableElementType, 21 | valueType: Type, 22 | spec: FormatSpecifier, 23 | starWidth: int, 24 | starPrecision: int 25 | ) = 26 | 27 | new(s: string, type': PrintableElementType) = 28 | PrintableElement( 29 | Unchecked.defaultof string>, 30 | s, 31 | type', 32 | Unchecked.defaultof, 33 | Unchecked.defaultof, 34 | NotSpecifiedValue, 35 | NotSpecifiedValue) 36 | 37 | /// The type of element (From a format specifier or directly from the string) 38 | member _.ElementType with get() = type' 39 | 40 | /// The value passed as parameter, of type ValueType 41 | member _.Value with get() = value 42 | 43 | /// The type of the value 44 | member _.ValueType 45 | with get() = 46 | match type' with 47 | | PrintableElementType.FromFormatSpecifier -> valueType 48 | | _ -> typeof 49 | 50 | /// The format specification for format specifiers 51 | member _.Specifier with get() = if Object.ReferenceEquals(spec, null) then None else Some(spec) 52 | 53 | // The .Net format string for format specifiers 54 | 55 | /// The width if specified via another parameter as in "%*i" 56 | member _.StarWidth with get() = match starWidth with NotSpecifiedValue -> None | x -> Some(x) 57 | 58 | /// The precision if specified via another parameter as in "%.*f" 59 | member _.StarPrecision with get() = match starPrecision with NotSpecifiedValue -> None | x -> Some(x) 60 | 61 | override x.ToString () = 62 | // The .Net format is handled differently as it's stored in the Specifier but not present 63 | // in it's ToString() representation 64 | let dotnetFormat = 65 | match x.Specifier with 66 | | Some x -> 67 | match x.InteropHoleDotNetFormat with 68 | | ValueSome x -> Some x 69 | | ValueNone -> None 70 | | None -> None 71 | let dotnetFormatAddition = 72 | match dotnetFormat with 73 | | Some x -> sprintf ", dotnetFormat: '%s'" x 74 | | None -> "" 75 | 76 | sprintf 77 | "value: %A, type: %A, valueType: %s, spec: %s%s, starWidth: %s, starPrecision: %s, AsPrintF: %s" 78 | value 79 | type' 80 | (x.ValueType.FullName) 81 | (match x.Specifier with Some x -> x.ToString() | None -> "") 82 | dotnetFormatAddition 83 | (match x.StarWidth with Some x -> x.ToString() | None -> "") 84 | (match x.StarPrecision with Some x -> x.ToString() | None -> "") 85 | (x.FormatAsPrintF()) 86 | 87 | /// Get the string representation that printf would have normally generated 88 | member _.FormatAsPrintF() = 89 | match type' with 90 | | PrintableElementType.FromFormatSpecifier -> printer () 91 | | _ -> value :?> string 92 | 93 | member internal _.IsNullOrEmpty with get() = 94 | match type' with 95 | | PrintableElementType.FromFormatSpecifier -> false 96 | | _ -> String.IsNullOrEmpty(value :?> string) 97 | -------------------------------------------------------------------------------- /src/BlackFox.MasterOfFoo.Tests/Test.fs: -------------------------------------------------------------------------------- 1 | module BlackFox.MasterOfFoo.DoPrintfTests 2 | 3 | open Expecto 4 | open System.Text 5 | 6 | open BlackFox.MasterOfFoo 7 | 8 | type TestEnv() = 9 | inherit PrintfEnv() 10 | let buf = StringBuilder() 11 | override __.Finalize() = buf.ToString () 12 | override __.Write(s : PrintableElement) = ignore(buf.Append(s.FormatAsPrintF())) 13 | override __.WriteT(s : string) = ignore(buf.Append(s)) 14 | 15 | let testprintf (format: Printf.StringFormat<'a>) = doPrintf format (fun _ -> TestEnv()) 16 | let coreprintf = FSharp.Core.Printf.sprintf 17 | 18 | type Discriminated = |A of string | B of int 19 | 20 | let testStr = "Foo" 21 | let testInt = 42 22 | 23 | type MyFormatable() = 24 | interface System.IFormattable with 25 | member __.ToString(format: string, _formatProvider: System.IFormatProvider) = 26 | $"MyFormatable(%s{format})" 27 | 28 | override _.ToString() = "MyFormatable" 29 | 30 | let tests = [ 31 | test "simple string" { 32 | Expect.equal 33 | (coreprintf "Foo") 34 | (testprintf "Foo") 35 | "Foo" 36 | } 37 | 38 | test "simple string interpolation" { 39 | Expect.equal 40 | (coreprintf $"Foo") 41 | (testprintf $"Foo") 42 | "Foo" 43 | } 44 | 45 | test "string format" { 46 | Expect.equal 47 | (coreprintf "%s" "Foo") 48 | (testprintf "%s" "Foo") 49 | "%s" 50 | } 51 | 52 | test "string format width" { 53 | Expect.equal 54 | (coreprintf "%1s" "Foo") 55 | (testprintf "%1s" "Foo") 56 | "%1s" 57 | Expect.equal 58 | (coreprintf "%5s" "Foo") 59 | (testprintf "%5s" "Foo") 60 | "%5s" 61 | } 62 | 63 | test "string untyped interpolation" { 64 | Expect.equal 65 | (coreprintf $"""{"Foo"}""") 66 | (testprintf $"""{"Foo"}""") 67 | "%s" 68 | } 69 | 70 | test "string typed interpolation" { 71 | Expect.equal 72 | (coreprintf $"""%s{"Foo"}""") 73 | (testprintf $"""%s{"Foo"}""") 74 | "%s" 75 | } 76 | 77 | test "int format %i" { 78 | Expect.equal 79 | (coreprintf "%i" 5) 80 | (testprintf "%i" 5) 81 | "%i" 82 | } 83 | 84 | test "int format %B" { 85 | Expect.equal 86 | (coreprintf "%B" 5) 87 | (testprintf "%B" 5) 88 | "%B" 89 | } 90 | 91 | test "int untyped interpolation" { 92 | Expect.equal 93 | (coreprintf $"{5}") 94 | (testprintf $"{5}") 95 | "{5}" 96 | } 97 | 98 | test "int untyped interpolation .NET format" { 99 | Expect.equal 100 | (coreprintf $"{System.Math.PI:N3}") 101 | (testprintf $"{System.Math.PI:N3}") 102 | "{System.Math.PI:N3}" 103 | } 104 | 105 | test "int typed interpolation" { 106 | Expect.equal 107 | (coreprintf $"%i{5}") 108 | (testprintf $"%i{5}") 109 | "%i" 110 | } 111 | 112 | test "int format width" { 113 | Expect.equal 114 | (coreprintf "%1i" 5) 115 | (testprintf "%1i" 5) 116 | "%1i" 117 | Expect.equal 118 | (coreprintf "%5i" 5) 119 | (testprintf "%5i" 5) 120 | "%5i" 121 | } 122 | 123 | test "A format" { 124 | Expect.equal 125 | (coreprintf "%A %A %A %A %A" "Foo" 5 (A("Foo")) (B(42)) System.ConsoleColor.Red) 126 | (testprintf "%A %A %A %A %A" "Foo" 5 (A("Foo")) (B(42)) System.ConsoleColor.Red) 127 | "%A %A %A %A %A" 128 | } 129 | 130 | test "custom untyped interpolation" { 131 | Expect.equal 132 | (coreprintf $"{MyFormatable()}") 133 | (testprintf $"{MyFormatable()}") 134 | "{MyFormatable()}" 135 | } 136 | 137 | test "custom untyped interpolation .NET format" { 138 | Expect.equal 139 | (coreprintf $"{MyFormatable():HelloWorld}") 140 | (testprintf $"{MyFormatable():HelloWorld}") 141 | "{MyFormatable():HelloWorld}" 142 | } 143 | 144 | test "custom untyped interpolation .NET format unusual characters" { 145 | // Using double backticks to escape the format string if it contains spaces for example 146 | Expect.equal 147 | (coreprintf $"{MyFormatable():``Hello World``}") 148 | (testprintf $"{MyFormatable():``Hello World``}") 149 | "{MyFormatable():``Hello World``}" 150 | } 151 | ] 152 | 153 | [] 154 | let test = testList "Tests" tests 155 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | # Uncomment if you have tasks that create the project's static files in wwwroot 27 | #wwwroot/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # DNX 43 | project.lock.json 44 | artifacts/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opendb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | *.VC.db 83 | 84 | # Visual Studio profiler 85 | *.psess 86 | *.vsp 87 | *.vspx 88 | *.sap 89 | 90 | # TFS 2012 Local Workspace 91 | $tf/ 92 | 93 | # Guidance Automation Toolkit 94 | *.gpState 95 | 96 | # ReSharper is a .NET coding add-in 97 | _ReSharper*/ 98 | *.[Rr]e[Ss]harper 99 | *.DotSettings.user 100 | 101 | # JustCode is a .NET coding add-in 102 | .JustCode 103 | 104 | # TeamCity is a build add-in 105 | _TeamCity* 106 | 107 | # DotCover is a Code Coverage Tool 108 | *.dotCover 109 | 110 | # NCrunch 111 | _NCrunch_* 112 | .*crunch*.local.xml 113 | nCrunchTemp_* 114 | 115 | # MightyMoose 116 | *.mm.* 117 | AutoTest.Net/ 118 | 119 | # Web workbench (sass) 120 | .sass-cache/ 121 | 122 | # Installshield output folder 123 | [Ee]xpress/ 124 | 125 | # DocProject is a documentation generator add-in 126 | DocProject/buildhelp/ 127 | DocProject/Help/*.HxT 128 | DocProject/Help/*.HxC 129 | DocProject/Help/*.hhc 130 | DocProject/Help/*.hhk 131 | DocProject/Help/*.hhp 132 | DocProject/Help/Html2 133 | DocProject/Help/html 134 | 135 | # Click-Once directory 136 | publish/ 137 | 138 | # Publish Web Output 139 | *.[Pp]ublish.xml 140 | *.azurePubxml 141 | 142 | # TODO: Un-comment the next line if you do not want to checkin 143 | # your web deploy settings because they may include unencrypted 144 | # passwords 145 | #*.pubxml 146 | *.publishproj 147 | 148 | # NuGet Packages 149 | *.nupkg 150 | # The packages folder can be ignored because of Package Restore 151 | **/packages/* 152 | # Uncomment if necessary however generally it will be regenerated when needed 153 | #!**/packages/repositories.config 154 | # NuGet v3's project.json files produces more ignoreable files 155 | *.nuget.props 156 | *.nuget.targets 157 | 158 | # Microsoft Azure Build Output 159 | csx/ 160 | *.build.csdef 161 | 162 | # Microsoft Azure Emulator 163 | ecf/ 164 | rcf/ 165 | 166 | # Microsoft Azure ApplicationInsights config file 167 | ApplicationInsights.config 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | [Ss]tyle[Cc]op.* 182 | ~$* 183 | *~ 184 | *.dbmdl 185 | *.dbproj.schemaview 186 | *.pfx 187 | *.publishsettings 188 | node_modules/ 189 | orleans.codegen.cs 190 | 191 | # RIA/Silverlight projects 192 | Generated_Code/ 193 | 194 | # Backup & report files from converting an old project file 195 | # to a newer Visual Studio version. Backup files are not needed, 196 | # because we have git ;-) 197 | _UpgradeReport_Files/ 198 | Backup*/ 199 | UpgradeLog*.XML 200 | UpgradeLog*.htm 201 | 202 | # SQL Server files 203 | *.mdf 204 | *.ldf 205 | 206 | # Business Intelligence projects 207 | *.rdl.data 208 | *.bim.layout 209 | *.bim_*.settings 210 | 211 | # Microsoft Fakes 212 | FakesAssemblies/ 213 | 214 | # GhostDoc plugin setting file 215 | *.GhostDoc.xml 216 | 217 | # Node.js Tools for Visual Studio 218 | .ntvs_analysis.dat 219 | 220 | # Visual Studio 6 build log 221 | *.plg 222 | 223 | # Visual Studio 6 workspace options file 224 | *.opt 225 | 226 | # Visual Studio LightSwitch build output 227 | **/*.HTMLClient/GeneratedArtifacts 228 | **/*.DesktopClient/GeneratedArtifacts 229 | **/*.DesktopClient/ModelManifest.xml 230 | **/*.Server/GeneratedArtifacts 231 | **/*.Server/ModelManifest.xml 232 | _Pvt_Extensions 233 | 234 | # LightSwitch generated files 235 | GeneratedArtifacts/ 236 | ModelManifest.xml 237 | 238 | # Paket dependency manager 239 | .paket/paket.exe 240 | paket-files/ 241 | 242 | # FAKE - F# Make 243 | .fake/ 244 | 245 | launchSettings.json 246 | .ionide/ 247 | .idea/ 248 | 249 | TestResults.xml 250 | 251 | # MSBuild binary logs 252 | *.binlog 253 | -------------------------------------------------------------------------------- /src/BlackFox.MasterOfFoo/PrintfEnv.fs: -------------------------------------------------------------------------------- 1 | namespace BlackFox.MasterOfFoo 2 | 3 | open System 4 | 5 | [] 6 | /// Represents one step in the execution of a format string 7 | type internal Step = 8 | | StepWithArg of prefix: PrintableElement * conv: (obj -> PrintableElement) 9 | | StepWithTypedArg of prefix: PrintableElement * conv: (obj -> Type -> PrintableElement) 10 | | StepString of prefix: PrintableElement 11 | | StepLittleT of prefix: PrintableElement 12 | | StepLittleA of prefix: PrintableElement 13 | | StepStar1 of prefix: PrintableElement * conv: (obj -> int -> PrintableElement) 14 | | StepPercentStar1 of prefix: PrintableElement 15 | | StepStar2 of prefix: PrintableElement * conv: (obj -> int -> int -> PrintableElement) 16 | | StepPercentStar2 of prefix: PrintableElement 17 | 18 | // Count the number of string fragments in a sequence of steps 19 | static member BlockCount(steps: Step[]) = 20 | let mutable count = 0 21 | for step in steps do 22 | match step with 23 | | StepWithArg (prefix, _conv) -> 24 | if not (prefix.IsNullOrEmpty) then count <- count + 1 25 | count <- count + 1 26 | | StepWithTypedArg (prefix, _conv) -> 27 | if not (prefix.IsNullOrEmpty) then count <- count + 1 28 | count <- count + 1 29 | | StepString prefix -> 30 | if not (prefix.IsNullOrEmpty) then count <- count + 1 31 | | StepLittleT(prefix) -> 32 | if not (prefix.IsNullOrEmpty) then count <- count + 1 33 | count <- count + 1 34 | | StepLittleA(prefix) -> 35 | if not (prefix.IsNullOrEmpty) then count <- count + 1 36 | count <- count + 1 37 | | StepStar1(prefix, _conv) -> 38 | if not (prefix.IsNullOrEmpty) then count <- count + 1 39 | count <- count + 1 40 | | StepPercentStar1(prefix) -> 41 | if not (prefix.IsNullOrEmpty) then count <- count + 1 42 | count <- count + 1 43 | | StepStar2(prefix, _conv) -> 44 | if not (prefix.IsNullOrEmpty) then count <- count + 1 45 | count <- count + 1 46 | | StepPercentStar2(prefix) -> 47 | if not (prefix.IsNullOrEmpty) then count <- count + 1 48 | count <- count + 1 49 | count 50 | 51 | /// Abstracts generated printer from the details of particular environment: how to write text, how to produce results etc... 52 | [] 53 | type PrintfEnv<'State, 'Residue, 'Result>(state: 'State) = 54 | member _.State = state 55 | 56 | /// Create the final result for this printer 57 | abstract Finalize: unit -> 'Result 58 | 59 | /// Write an element from the format string (Raw text or format specifier) to the printer 60 | abstract Write: PrintableElement -> unit 61 | 62 | /// Write the result of a '%t' format. If this is a string it is written. If it is a 'unit' value 63 | /// the side effect has already happened 64 | abstract WriteT: 'Residue -> unit 65 | 66 | member env.WriteSkipEmpty(s: PrintableElement) = 67 | if not (s.IsNullOrEmpty) then 68 | env.Write s 69 | 70 | member internal env.RunSteps (args: obj[], argTys: Type[], steps: Step[]) = 71 | let mutable argIndex = 0 72 | let mutable tyIndex = 0 73 | 74 | for step in steps do 75 | match step with 76 | | StepWithArg (prefix, conv) -> 77 | env.WriteSkipEmpty prefix 78 | let arg = args.[argIndex] 79 | argIndex <- argIndex + 1 80 | env.Write(conv arg) 81 | 82 | | StepWithTypedArg (prefix, conv) -> 83 | env.WriteSkipEmpty prefix 84 | let arg = args.[argIndex] 85 | let argTy = argTys.[tyIndex] 86 | argIndex <- argIndex + 1 87 | tyIndex <- tyIndex + 1 88 | env.Write(conv arg argTy) 89 | 90 | | StepString prefix -> 91 | env.WriteSkipEmpty prefix 92 | 93 | | StepLittleT(prefix) -> 94 | env.WriteSkipEmpty prefix 95 | let farg = args.[argIndex] 96 | argIndex <- argIndex + 1 97 | let f = farg :?> ('State -> 'Residue) 98 | env.WriteT(f env.State) 99 | 100 | | StepLittleA(prefix) -> 101 | env.WriteSkipEmpty prefix 102 | let farg = args.[argIndex] 103 | argIndex <- argIndex + 1 104 | let arg = args.[argIndex] 105 | argIndex <- argIndex + 1 106 | let f = farg :?> ('State -> obj -> 'Residue) 107 | env.WriteT(f env.State arg) 108 | 109 | | StepStar1(prefix, conv) -> 110 | env.WriteSkipEmpty prefix 111 | let star1 = args.[argIndex] :?> int 112 | argIndex <- argIndex + 1 113 | let arg1 = args.[argIndex] 114 | argIndex <- argIndex + 1 115 | env.Write (conv arg1 star1) 116 | 117 | | StepPercentStar1(prefix) -> 118 | argIndex <- argIndex + 1 119 | env.WriteSkipEmpty prefix 120 | env.Write(PrintableElement("%", PrintableElementType.MadeByEngine)) 121 | 122 | | StepStar2(prefix, conv) -> 123 | env.WriteSkipEmpty prefix 124 | let star1 = args.[argIndex] :?> int 125 | argIndex <- argIndex + 1 126 | let star2 = args.[argIndex] :?> int 127 | argIndex <- argIndex + 1 128 | let arg1 = args.[argIndex] 129 | argIndex <- argIndex + 1 130 | env.Write (conv arg1 star1 star2) 131 | 132 | | StepPercentStar2(prefix) -> 133 | env.WriteSkipEmpty prefix 134 | argIndex <- argIndex + 2 135 | env.Write(PrintableElement("%", PrintableElementType.MadeByEngine)) 136 | 137 | env.Finalize() 138 | 139 | /// This is the new name of Finalize in the new version of FSharp.Core 140 | /// 141 | /// We can't rename our method without breaking customers code but changing the name everywhere in printf.fs is 142 | /// also a pain. So as an alternative this internal version, made only to ease porting was introduced. 143 | member internal env.Finish () = 144 | env.Finalize() 145 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # MasterOfFoo 2 | 3 | !["f" Logo](https://raw.githubusercontent.com/vbfox/MasterOfFoo/master/src/BlackFox.MasterOfFoo/Icon.png) 4 | 5 | 6 | [![Github Actions Status](https://github.com/vbfox/MasterOfFoo/actions/workflows/main.yml/badge.svg)](https://github.com/vbfox/MasterOfFoo/actions/workflows/main.yml?query=branch%3Amaster) 7 | [![Nuget Package](https://img.shields.io/nuget/v/BlackFox.MasterOfFoo.svg)](https://www.nuget.org/packages/BlackFox.MasterOfFoo) 8 | 9 | A library to allow using `printf` style strings in more places. 10 | 11 | The code is essentially an extracted version of [`printf.fs`][printf_fs] where the environment can not only decide 12 | what to do with the final blocks that compose the string (printf put them on the console, sprintf in a buffer, ...) 13 | but also what to do with the parameters passed for each format specifier. 14 | 15 | ## Sample usage 16 | 17 | ```fsharp 18 | module MyModule = 19 | open System.Text 20 | open BlackFox.MasterOfFoo 21 | type private MySprintfEnv() = 22 | inherit PrintfEnv() 23 | let buf = StringBuilder() 24 | override this.Finalize() = buf.ToString () 25 | override this.Write(s : PrintableElement) = ignore(buf.Append(s.FormatAsPrintF())) 26 | override this.WriteT(s : string) = ignore(buf.Append(s)) 27 | 28 | let mysprintf (format: Format<'T, unit, string, string>) = 29 | doPrintfFromEnv format (MySprintfEnv()) 30 | 31 | MyModule.mysprintf "Hello %s." "World" 32 | ``` 33 | 34 | ## Mini-Doc 35 | 36 | ### PrintableElement 37 | 38 | `PrintableElement` represent an element in a string, for example `sprintf "Foo %s bar" "x"` produce 3 39 | `PrintableElement`, the first contains the string `"Foo "` the second is a format specifier `'s'` with an associated 40 | string value `"x"` and then there is the string the string `" Bar"`. 41 | 42 | Members : 43 | 44 | * `ElementType`: Tell your if this is a string or a format specifier. 45 | * `Value`: give the value if it was a format specifier. 46 | * `ValueType`: give the type of value expected by the format specifier. 47 | * `StarWidth`: The width if specified via another parameter as in "%*i". 48 | * `StarPrecision`: The precision if specified via another parameter as in "%.*f". 49 | * `FormatAsPrintF()`: Get the string representation that printf would have normally generated. 50 | * `Specifier`: The format specification for format specifiers. 51 | 52 | ### PrintfEnv 53 | 54 | `PrintfEnv` is the type to implement to create a printf variant it has 3 type parameters: 55 | 56 | * `'State`: The state of the printer, passed as argument when using '%t'. 57 | * `'Residue`: The type that methods passed to '%t' must return. 58 | * `'Result`: The final result type for the printer. 59 | 60 | Members: 61 | * `Finalize`: Create the final result for this printer 62 | * `Write`: Write an element from the format string to the printer 63 | * `WriteT`: Write the result of the method provided by %t to the printer. 64 | 65 | ### Functions 66 | 67 | * `doPrintfFromEnv`: Take a format and a `PrintfEnv` to create a printf-like function 68 | * `doPrintf`: Same as `doPrintfFromEnv` but allow to know the number of elements when the `PrintfEnv` is created. 69 | 70 | ## FAQ 71 | 72 | ### What does it allow exactly that can't be done with the original set of functions ? 73 | 74 | * Generating complex object that aren't only a string like an `SqlCommand` or structured logging. 75 | * Escaping parts in strings, like an `xmlprintf` that would escape `<` to `<` in parameters but not in the format 76 | string. 77 | 78 | ### What are the limitations ? 79 | 80 | The main limitation is that the F# compiler allow a strict set of things an you can't go differently. 81 | The function signature that is the first argument to `Format<_,_,_,_,>` is generated from rules in the compiler and no 82 | library can change them. 83 | 84 | The consequence is that we're limited to what is present in the F# compiler, can't add a `%Z` or allow `%0s` to work. 85 | 86 | ### Aren't you just replicating `ksprintf` ? 87 | 88 | `ksprintf` allow you to run code on the final generated result, essentially allowing you to run code during 89 | `PrintfEnv.Finalize` but you can't manipualte the format specifiers or their parameters. 90 | 91 | ### What this `Star` syntax 92 | 93 | When `*` is specified for either the width or the precision an additional parameter is taken by the format to get the 94 | value. 95 | 96 | ```` 97 | > sprintf "%*.*f";; 98 | val it : (int -> int -> float -> string) = 99 | ```` 100 | 101 | ### How are interpolated strings represented ? 102 | 103 | The details of string interpolation internals are specified in [F# RFC FS-1001 - String Interpolation][fs-1001]. 104 | 105 | They appear as follow in this library: 106 | * Type-checked "printf-style" fills behave exactly as they do in `sprintf` and friends. 107 | * Unchecked ".NET-style" fills appear with a `Specifier.TypeChar` of `'P'` and the .NET format string 108 | in `Specifier.InteropHoleDotNetFormat`. 109 | 110 | [fs-1001]: https://github.com/fsharp/fslang-design/blob/aca88da13cdb95f4f337d4f7d44cbf9d343704ae/FSharp-5.0/FS-1001-StringInterpolation.md#f-rfc-fs-1001---string-interpolation 111 | 112 | ## Projects using it 113 | 114 | * [ColoredPrintf][colorprintf]: A small library that I created to add colored parts to printf strings. 115 | * [FSharp.Logf][Logf]: printf-style functions for `Microsoft.Extensions.Logging.ILogger`. 116 | 117 | *If you use it somewhere, ping me on the fediverse [@vbfox@hachyderm.io][fedi] so I can add you.* 118 | 119 | [Logf]: https://github.com/jwosty/FSharp.Logf 120 | 121 | More fun ? 122 | ---------- 123 | 124 | ```fsharp 125 | module ColorPrintf = 126 | open System 127 | open System.Text 128 | open BlackFox.MasterOfFoo 129 | 130 | type private Colorize<'Result>(k) = 131 | inherit PrintfEnv() 132 | override this.Finalize() : 'Result = k() 133 | override this.Write(s : PrintableElement) = 134 | match s.ElementType with 135 | | PrintableElementType.FromFormatSpecifier -> 136 | let color = Console.ForegroundColor 137 | Console.ForegroundColor <- ConsoleColor.Blue 138 | Console.Write(s.FormatAsPrintF()) 139 | Console.ForegroundColor <- color 140 | | _ -> Console.Write(s.FormatAsPrintF()) 141 | override this.WriteT(s : string) = 142 | let color = Console.ForegroundColor 143 | Console.ForegroundColor <- ConsoleColor.Red 144 | Console.Write(s) 145 | Console.ForegroundColor <- color 146 | 147 | let colorprintf (format: Format<'T, unit, string, unit>) = 148 | doPrintfFromEnv format (Colorize id) 149 | 150 | ColorPrintf.colorprintf "%s est %t" "La vie" (fun _ -> "belle !") 151 | ``` 152 | 153 | [printf_fs]: https://github.com/dotnet/fsharp/blob/main/src/FSharp.Core/printf.fs 154 | [fedi]: https://hachyderm.io/@vbfox 155 | [colorprintf]: https://github.com/vbfox/ColoredPrintf 156 | -------------------------------------------------------------------------------- /src/BlackFox.MasterOfFoo.Build/Tasks.fs: -------------------------------------------------------------------------------- 1 | module BlackFox.MasterOfFoo.Build.Tasks 2 | 3 | open Fake.Api 4 | open Fake.Core 5 | open Fake.DotNet 6 | open Fake.DotNet.Testing 7 | open Fake.IO 8 | open Fake.IO.Globbing.Operators 9 | open Fake.IO.FileSystemOperators 10 | open Fake.Tools 11 | 12 | open BlackFox 13 | open BlackFox.Fake 14 | open System.Xml.Linq 15 | 16 | let testProjectName = "BlackFox.MasterOfFoo.Tests" 17 | 18 | let createAndGetDefault () = 19 | let configuration = DotNet.BuildConfiguration.fromEnvironVarOrDefault "configuration" DotNet.BuildConfiguration.Release 20 | 21 | let rootDir = System.IO.Path.GetFullPath(__SOURCE_DIRECTORY__ ".." "..") 22 | let srcDir = rootDir "src" 23 | let artifactsDir = rootDir "artifacts" 24 | 25 | let libraryProjectFile = srcDir "BlackFox.MasterOfFoo" "BlackFox.MasterOfFoo.fsproj" 26 | let libraryBinDir = artifactsDir "BlackFox.MasterOfFoo" (string configuration) 27 | let solutionFile = srcDir "MasterOfFoo.sln" 28 | let projects = 29 | GlobbingPattern.createFrom srcDir 30 | ++ "**/*.*proj" 31 | -- "*.Build/*" 32 | 33 | /// The profile where the project is posted 34 | let gitOwner = "vbfox" 35 | let gitHome = "https://github.com/" + gitOwner 36 | 37 | /// The name of the project on GitHub 38 | let gitName = "MasterOfFoo" 39 | 40 | let getUnionCaseName (x:'a) = 41 | match Microsoft.FSharp.Reflection.FSharpValue.GetUnionFields(x, typeof<'a>) with | case, _ -> case.Name 42 | 43 | let release = 44 | let fromFile = ReleaseNotes.load (rootDir "Release Notes.md") 45 | if BuildServer.buildServer <> BuildServer.LocalBuild then 46 | let buildServerName = (getUnionCaseName BuildServer.buildServer).ToLowerInvariant() 47 | let nugetVer = sprintf "%s-%s.%s" fromFile.NugetVersion buildServerName BuildServer.buildVersion 48 | ReleaseNotes.ReleaseNotes.New(fromFile.AssemblyVersion, nugetVer, fromFile.Date, fromFile.Notes) 49 | else 50 | fromFile 51 | 52 | Trace.setBuildNumber release.NugetVersion 53 | 54 | let nupkgFile = libraryBinDir (sprintf "BlackFox.MasterOfFoo.%s.nupkg" release.NugetVersion) 55 | 56 | let writeVersionProps() = 57 | let doc = 58 | XDocument( 59 | XElement(XName.Get("Project"), 60 | XElement(XName.Get("PropertyGroup"), 61 | XElement(XName.Get "Version", release.NugetVersion), 62 | XElement(XName.Get "PackageReleaseNotes", String.toLines release.Notes)))) 63 | let path = artifactsDir "Version.props" 64 | System.IO.File.WriteAllText(path, doc.ToString()) 65 | 66 | let init = BuildTask.create "Init" [] { 67 | Directory.create artifactsDir 68 | } 69 | 70 | let clean = BuildTask.create "Clean" [init] { 71 | let objDirs = projects |> Seq.map(fun p -> System.IO.Path.GetDirectoryName(p) "obj") |> List.ofSeq 72 | Shell.cleanDirs (artifactsDir :: objDirs) 73 | } 74 | 75 | let generateVersionInfo = BuildTask.create "GenerateVersionInfo" [init; clean.IfNeeded] { 76 | writeVersionProps () 77 | AssemblyInfoFile.createFSharp (artifactsDir "Version.fs") [AssemblyInfo.Version release.AssemblyVersion] 78 | } 79 | 80 | let build = BuildTask.create "Build" [generateVersionInfo; clean.IfNeeded] { 81 | DotNet.build 82 | (fun p -> { p with Configuration = configuration }) 83 | solutionFile 84 | } 85 | 86 | let runTests = BuildTask.create "RunTests" [build] { 87 | let baseTestDir = artifactsDir testProjectName (string configuration) 88 | let testConfs = ["netcoreapp2.0", ".dll"; "net5.0", ".dll"; "net8.0", ".dll"] 89 | 90 | testConfs 91 | |> List.map (fun (fw, ext) -> baseTestDir fw (testProjectName + ext)) 92 | |> Expecto.run (fun p -> 93 | { p with 94 | PrintVersion = false 95 | FailOnFocusedTests = true 96 | }) 97 | 98 | for (fw, _) in testConfs do 99 | let dir = baseTestDir fw 100 | let outFile = sprintf "TestResults_%s.xml" (fw.Replace('.', '_')) 101 | File.delete (dir outFile) 102 | (dir "TestResults.xml") |> Shell.rename (dir outFile) 103 | Trace.publish (ImportData.Nunit NunitDataVersion.Nunit) (dir outFile) 104 | } 105 | 106 | let nuget = BuildTask.create "NuGet" [build;runTests.IfNeeded] { 107 | DotNet.pack 108 | (fun p -> { p with Configuration = configuration }) 109 | libraryProjectFile 110 | 111 | Trace.publish ImportData.BuildArtifact nupkgFile 112 | } 113 | 114 | let publishNuget = BuildTask.create "PublishNuget" [nuget] { 115 | let key = 116 | match Environment.environVarOrNone "nuget-key" with 117 | | Some(key) -> key 118 | | None -> UserInput.getUserPassword "NuGet key: " 119 | 120 | Paket.pushFiles 121 | (fun o -> { o with ApiKey = key; WorkingDir = rootDir }) 122 | [nupkgFile] 123 | } 124 | 125 | let zipFile = artifactsDir (sprintf "BlackFox.MasterOfFoo-%s.zip" release.NugetVersion) 126 | 127 | let zip = BuildTask.create "Zip" [build;runTests.IfNeeded] { 128 | let comment = sprintf "MasterOfFoo v%s" release.NugetVersion 129 | GlobbingPattern.createFrom libraryBinDir 130 | ++ "**/*.dll" 131 | ++ "**/*.xml" 132 | -- "**/FSharp.Core.*" 133 | |> Zip.createZip libraryBinDir zipFile comment 9 false 134 | 135 | Trace.publish ImportData.BuildArtifact zipFile 136 | } 137 | 138 | let gitRelease = BuildTask.create "GitRelease" [nuget.IfNeeded] { 139 | let remote = 140 | Git.CommandHelper.getGitResult "" "remote -v" 141 | |> Seq.filter (fun (s: string) -> s.EndsWith("(push)")) 142 | |> Seq.tryFind (fun (s: string) -> s.Contains(gitOwner + "/" + gitName)) 143 | |> function None -> gitHome + "/" + gitName | Some (s: string) -> s.Split().[0] 144 | 145 | Git.Branches.tag "" release.NugetVersion 146 | Git.Branches.pushTag "" remote release.NugetVersion 147 | } 148 | 149 | let gitHubRelease = BuildTask.create "GitHubRelease" [zip; gitRelease.IfNeeded] { 150 | let user = 151 | match Environment.environVarOrNone "github-user" with 152 | | Some s -> s 153 | | _ -> UserInput.getUserInput "GitHub Username: " 154 | let pw = 155 | match Environment.environVarOrNone "github-pw" with 156 | | Some s -> s 157 | | _ -> UserInput.getUserPassword "GitHub Password or Token: " 158 | 159 | // release on github 160 | GitHub.createClient user pw 161 | |> GitHub.draftNewRelease 162 | gitOwner 163 | gitName 164 | release.NugetVersion 165 | (release.SemVer.PreRelease <> None) 166 | (release.Notes) 167 | |> GitHub.uploadFile zipFile 168 | |> GitHub.publishDraft 169 | |> Async.RunSynchronously 170 | } 171 | 172 | let _releaseTask = BuildTask.createEmpty "Release" [clean; gitRelease; gitHubRelease; publishNuget] 173 | let _ciTask = BuildTask.createEmpty "CI" [clean; runTests; zip; nuget] 174 | 175 | BuildTask.createEmpty "Default" [runTests] 176 | -------------------------------------------------------------------------------- /src/TestApp/Program.fs: -------------------------------------------------------------------------------- 1 | open System.Text 2 | open BlackFox.MasterOfFoo 3 | open System.Data.SqlClient 4 | open System.Data.Common 5 | 6 | module ReimplementPrintf = 7 | open System 8 | type StringPrintfEnv<'Result>(k, n) = 9 | inherit PrintfEnv(()) 10 | 11 | let buf : string[] = Array.zeroCreate n 12 | let mutable ptr = 0 13 | 14 | override this.Finalize() : 'Result = k (String.Concat(buf)) 15 | override this.Write(s : PrintableElement) = 16 | buf.[ptr] <- s.ToString() 17 | ptr <- ptr + 1 18 | override this.WriteT(s) = 19 | buf.[ptr] <- s 20 | ptr <- ptr + 1 21 | 22 | type StringBuilderPrintfEnv<'Result>(k, buf) = 23 | inherit PrintfEnv(buf) 24 | override this.Finalize() : 'Result = k () 25 | override this.Write(s : PrintableElement) = ignore(buf.Append(s.ToString())) 26 | override this.WriteT(()) = () 27 | 28 | type TextWriterPrintfEnv<'Result>(k, tw : IO.TextWriter) = 29 | inherit PrintfEnv(tw) 30 | override this.Finalize() : 'Result = k() 31 | override this.Write(s : PrintableElement) = tw.Write (s.ToString()) 32 | override this.WriteT(()) = () 33 | 34 | type BuilderFormat<'T,'Result> = Format<'T, System.Text.StringBuilder, unit, 'Result> 35 | type StringFormat<'T,'Result> = Format<'T, unit, string, 'Result> 36 | type TextWriterFormat<'T,'Result> = Format<'T, System.IO.TextWriter, unit, 'Result> 37 | type BuilderFormat<'T> = BuilderFormat<'T,unit> 38 | type StringFormat<'T> = StringFormat<'T,string> 39 | type TextWriterFormat<'T> = TextWriterFormat<'T,unit> 40 | 41 | [] 42 | let ksprintf continuation (format : Format<'T, unit, string, 'Result>) : 'T = 43 | doPrintf format (fun n -> StringPrintfEnv(continuation, n)) 44 | 45 | [] 46 | let sprintf (format : StringFormat<'T>) = ksprintf id format 47 | 48 | [] 49 | let kprintf f fmt = ksprintf f fmt 50 | 51 | [] 52 | let kbprintf f (buf: System.Text.StringBuilder) fmt = 53 | doPrintfFromEnv fmt (StringBuilderPrintfEnv(f, buf)) 54 | 55 | [] 56 | let kfprintf f os fmt = doPrintfFromEnv fmt (TextWriterPrintfEnv(f, os)) 57 | 58 | [] 59 | let bprintf buf fmt = kbprintf ignore buf fmt 60 | 61 | [] 62 | let fprintf (os: System.IO.TextWriter) fmt = kfprintf ignore os fmt 63 | 64 | [] 65 | let fprintfn (os: System.IO.TextWriter) fmt = kfprintf (fun _ -> os.WriteLine()) os fmt 66 | 67 | [] 68 | let failwithf fmt = ksprintf failwith fmt 69 | 70 | [] 71 | let printf fmt = fprintf System.Console.Out fmt 72 | 73 | [] 74 | let eprintf fmt = fprintf System.Console.Error fmt 75 | 76 | [] 77 | let printfn fmt = fprintfn System.Console.Out fmt 78 | 79 | [] 80 | let eprintfn fmt = fprintfn System.Console.Error fmt 81 | 82 | type internal SqlEnv<'cmd when 'cmd :> DbCommand>(n: int, command: 'cmd) = 83 | inherit PrintfEnv(()) 84 | let queryString = StringBuilder() 85 | let mutable index = 0 86 | 87 | let addParameter (p: DbParameter) = 88 | ignore(queryString.Append p.ParameterName) 89 | command.Parameters.Add p |> ignore 90 | 91 | override __.Finalize() = 92 | command.CommandText <- queryString.ToString() 93 | command 94 | 95 | override __.Write(s : PrintableElement) = 96 | let asPrintf = s.FormatAsPrintF() 97 | match s.ElementType with 98 | | PrintableElementType.FromFormatSpecifier -> 99 | let parameter = 100 | if typeof.IsAssignableFrom(s.ValueType) then 101 | s.Value :?> DbParameter 102 | else 103 | let paramName = sprintf "@p%i" index 104 | index <- index + 1 105 | 106 | let parameter = command.CreateParameter() 107 | parameter.ParameterName <- paramName 108 | parameter.Value <- s.Value 109 | parameter 110 | 111 | addParameter parameter 112 | | _ -> 113 | ignore(queryString.Append asPrintf) 114 | 115 | override __.WriteT(()) = () 116 | 117 | let sqlCommandf (format : Format<'T, unit, unit, SqlCommand>) = 118 | MasterOfFoo.doPrintf format (fun n -> SqlEnv(n, new SqlCommand ()) :> PrintfEnv<_, _, _>) 119 | 120 | module ColorPrintf = 121 | open System 122 | 123 | type private Colorize<'Result>(k) = 124 | inherit PrintfEnv() 125 | override this.Finalize() : 'Result = k() 126 | override this.Write(s : PrintableElement) = 127 | match s.ElementType with 128 | | PrintableElementType.FromFormatSpecifier -> 129 | let color = Console.ForegroundColor 130 | Console.ForegroundColor <- ConsoleColor.Blue 131 | Console.Write(s.FormatAsPrintF()) 132 | Console.ForegroundColor <- color 133 | | _ -> Console.Write(s.FormatAsPrintF()) 134 | override this.WriteT(s : string) = 135 | let color = Console.ForegroundColor 136 | Console.ForegroundColor <- ConsoleColor.Red 137 | Console.Write(s) 138 | Console.ForegroundColor <- color 139 | 140 | let colorprintf (format: Format<'T, unit, string, unit>) = 141 | doPrintfFromEnv format (Colorize id) 142 | 143 | //MyModule.mysprintf "Hello %s.\n" "World" 144 | 145 | type internal QueryStringEnv() = 146 | inherit PrintfEnv(StringBuilder()) 147 | 148 | override this.Finalize() = this.State.ToString() 149 | 150 | override this.Write(s : PrintableElement) = 151 | let asPrintf = s.FormatAsPrintF() 152 | match s.ElementType with 153 | | PrintableElementType.FromFormatSpecifier -> 154 | let escaped = System.Uri.EscapeDataString(asPrintf) 155 | ignore(this.State.Append escaped) 156 | | _ -> 157 | ignore(this.State.Append asPrintf) 158 | 159 | override this.WriteT(()) = () 160 | 161 | let queryStringf (format : Format<'T, StringBuilder, unit, string>) = 162 | MasterOfFoo.doPrintf format (fun _ -> QueryStringEnv() :> PrintfEnv<_, _, _>) 163 | 164 | type internal MyTestEnv<'Result>(k, state) = 165 | inherit PrintfEnv(state) 166 | override this.Finalize() : 'Result = 167 | printfn "Finalizing" 168 | k () 169 | override this.Write(s : PrintableElement) = 170 | printfn "Writing: %A" s 171 | state.Append(s.FormatAsPrintF()) |> ignore 172 | override this.WriteT(()) = 173 | printfn "WTF" 174 | 175 | let testprintf (sb: StringBuilder) (format : Format<'T, StringBuilder, unit, unit>) = 176 | MasterOfFoo.doPrintf format (fun n -> 177 | MyTestEnv(ignore, sb) :> PrintfEnv<_, _, _> 178 | ) 179 | 180 | let title s = 181 | printfn $"%s{s}" 182 | printfn $"%s{System.String('-', s.Length)}" 183 | 184 | let endOfBlock () = 185 | printfn "" 186 | printfn "" 187 | 188 | let debugSimple () = 189 | title "Simple" 190 | 191 | let sb = StringBuilder () 192 | testprintf sb "Hello %0-10i hello %s %A" 1000 "World" "World" 193 | sb.Clear() |> ignore 194 | testprintf sb "Hello %-010i hello %s" 1000 "World" 195 | System.Console.WriteLine("RESULT: {0}", sb.ToString()) 196 | 197 | endOfBlock () 198 | 199 | let debugPercentStar () = 200 | title "Percent Star" 201 | let sb = StringBuilder () 202 | testprintf sb "Hello '%*i'" 5 42 203 | sb.Clear() |> ignore 204 | testprintf sb "Hello '%.*f'" 3 42.12345 205 | sb.Clear() |> ignore 206 | testprintf sb "Hello '%*.*f'" 5 3 42.12345 207 | System.Console.WriteLine("RESULT: {0}", sb.ToString()) 208 | 209 | endOfBlock () 210 | 211 | let debugChained () = 212 | title "Chained" 213 | let sb = StringBuilder () 214 | testprintf sb "Hello %s %s %s %s %s %s" "1" "2" "3" "4" "5" "6" 215 | System.Console.WriteLine("RESULT: {0}", sb.ToString()) 216 | 217 | endOfBlock () 218 | 219 | let debugComplex () = 220 | title "Complex" 221 | let sb = StringBuilder () 222 | testprintf sb "Hello %s %s %s %s %s %s %06i %t" "1" "2" "3" "4" "5" "6" 5 (fun x -> x.Append("CALLED") |> ignore) 223 | System.Console.WriteLine("RESULT: {0}", sb.ToString()) 224 | 225 | endOfBlock () 226 | 227 | let demoQueryString () = 228 | title "Query String" 229 | 230 | let foo = "#baz" 231 | let bar = "++Hello world && problem for arg √" 232 | let work = 1 233 | let result = queryStringf $"hello/uri+with space/x?foo=%s{foo}&bar=%s{bar}&work=%i{work}" 234 | printfn $"%s{result}" 235 | 236 | endOfBlock() 237 | 238 | let demoColorPrintf () = 239 | title "Color Printf" 240 | 241 | ColorPrintf.colorprintf "%s est %t" "La vie" (fun _ -> "belle !\n") 242 | 243 | endOfBlock() 244 | 245 | let demoColorPrintfInterpolated () = 246 | title "Color Printf Interpolated" 247 | 248 | let name = "Phillip" 249 | let age = 30 250 | let tf = (fun _ -> "Hello TF") 251 | 252 | ColorPrintf.colorprintf $"Name: {name} %s{name}, Age: {age}, tf: %t{tf}, %A{age}\n" 253 | 254 | endOfBlock() 255 | 256 | let demoSqlCommand () = 257 | title "SQL Command" 258 | 259 | let cmd: SqlCommand = 260 | sqlCommandf 261 | "SELECT * FROM tbUser WHERE UserId=%i AND NAME=%s AND CREATIONDATE > %O" 262 | 5 263 | "Test" 264 | System.DateTimeOffset.Now 265 | 266 | printfn "Command: %s " cmd.CommandText 267 | for p in cmd.Parameters do 268 | printfn " - %s = %O" p.ParameterName p.Value 269 | 270 | endOfBlock() 271 | 272 | [] 273 | let main argv = 274 | if argv.Length > 0 && argv.[0].ToLowerInvariant() = "debug" then 275 | debugSimple () 276 | debugPercentStar () 277 | debugChained () 278 | debugComplex () 279 | else 280 | demoQueryString () 281 | demoColorPrintf () 282 | demoColorPrintfInterpolated () 283 | demoSqlCommand () 284 | 0 285 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/BlackFox.MasterOfFoo.Tests/InternalRepresentationTests.fs: -------------------------------------------------------------------------------- 1 | module BlackFox.MasterOfFoo.Tests.InternalRepresentationTests 2 | 3 | open Expecto 4 | open System.Text 5 | 6 | open BlackFox.MasterOfFoo 7 | open System.Text.RegularExpressions 8 | 9 | type MyFormatable() = 10 | interface System.IFormattable with 11 | member __.ToString(format: string, _formatProvider: System.IFormatProvider) = 12 | $"MyFormatable(%s{format})" 13 | 14 | override _.ToString() = "MyFormatable" 15 | 16 | type MyToStringSaysHi() = 17 | override _.ToString() = 18 | "Hi" 19 | 20 | type TestEnv() = 21 | inherit PrintfEnv() 22 | let buf = StringBuilder().AppendLine("Init") 23 | override _.Finalize() = 24 | buf.AppendLine("Finalize") |> ignore 25 | buf.ToString () 26 | override _.Write(s : PrintableElement) = 27 | buf.Append("Write ") |> ignore 28 | buf.Append(sprintf "%A" s) |> ignore 29 | buf.AppendLine(";") |> ignore 30 | override _.WriteT(s : string) = 31 | buf.Append("WriteT ") |> ignore 32 | buf.AppendLine(s) |> ignore 33 | 34 | let cleanTypeParameters (s: string) = 35 | let re = Regex("""\[\[([^,]+)([^\]]*)\]\]""") 36 | let replacement (m: Match) = 37 | sprintf "[[%s]]" m.Groups.[1].Value 38 | re.Replace(s, replacement) 39 | 40 | let testprintf (format: Printf.StringFormat<'a>) = 41 | doPrintf format (fun _ -> TestEnv()) 42 | 43 | let cleanText (s: string) = 44 | s.Trim().Replace("\r\n", "\n") 45 | 46 | let testEqual (actual: string) (expected:string) = 47 | Expect.equal (actual |> cleanText |> cleanTypeParameters) (expected |> cleanText) "" 48 | 49 | let tests = [ 50 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 51 | // Simple case 52 | 53 | test "simple string" { 54 | testEqual 55 | (testprintf "Foo") 56 | """ 57 | Init 58 | Write value: "Foo", type: Direct, valueType: System.String, spec: , starWidth: , starPrecision: , AsPrintF: Foo; 59 | Finalize 60 | """ 61 | } 62 | 63 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 64 | // Supported types 65 | 66 | test "string hole only" { 67 | testEqual 68 | (testprintf "%s" "Foo") 69 | """ 70 | Init 71 | Write value: "Foo", type: FromFormatSpecifier, valueType: System.String, spec: 's', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: Foo; 72 | Finalize 73 | """ 74 | } 75 | 76 | test "boolean hole only" { 77 | testEqual 78 | (testprintf "%b" true) 79 | """ 80 | Init 81 | Write value: true, type: FromFormatSpecifier, valueType: System.Boolean, spec: 'b', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: true; 82 | Finalize 83 | """ 84 | } 85 | 86 | test "int hole only %i" { 87 | testEqual 88 | (testprintf "%i" 42) 89 | """ 90 | Init 91 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'i', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 42; 92 | Finalize 93 | """ 94 | } 95 | 96 | test "int hole only %d" { 97 | testEqual 98 | (testprintf "%d" 42) 99 | """ 100 | Init 101 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'd', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 42; 102 | Finalize 103 | """ 104 | } 105 | 106 | test "int hole only %u" { 107 | testEqual 108 | (testprintf "%u" 42) 109 | """ 110 | Init 111 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'u', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 42; 112 | Finalize 113 | """ 114 | } 115 | 116 | test "int hole only %x" { 117 | testEqual 118 | (testprintf "%x" 42) 119 | """ 120 | Init 121 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'x', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 2a; 122 | Finalize 123 | """ 124 | } 125 | 126 | test "int hole only %X" { 127 | testEqual 128 | (testprintf "%X" 42) 129 | """ 130 | Init 131 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'X', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 2A; 132 | Finalize 133 | """ 134 | } 135 | 136 | test "int hole only %o" { 137 | testEqual 138 | (testprintf "%o" 42) 139 | """ 140 | Init 141 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'o', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 52; 142 | Finalize 143 | """ 144 | } 145 | 146 | test "int hole only %B" { 147 | testEqual 148 | (testprintf "%B" 42) 149 | """ 150 | Init 151 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'B', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 101010; 152 | Finalize 153 | """ 154 | } 155 | 156 | test "float64 hole only %f" { 157 | testEqual 158 | (testprintf "%f" 42.42) 159 | """ 160 | Init 161 | Write value: 42.42, type: FromFormatSpecifier, valueType: System.Double, spec: 'f', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 42.420000; 162 | Finalize 163 | """ 164 | } 165 | 166 | test "float64 hole only %e" { 167 | testEqual 168 | (testprintf "%e" 42.42) 169 | """ 170 | Init 171 | Write value: 42.42, type: FromFormatSpecifier, valueType: System.Double, spec: 'e', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 4.242000e+001; 172 | Finalize 173 | """ 174 | } 175 | 176 | test "float64 hole only %E" { 177 | testEqual 178 | (testprintf "%E" 42.42) 179 | """ 180 | Init 181 | Write value: 42.42, type: FromFormatSpecifier, valueType: System.Double, spec: 'E', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 4.242000E+001; 182 | Finalize 183 | """ 184 | } 185 | 186 | test "float64 hole only %g" { 187 | testEqual 188 | (testprintf "%g" 42.42) 189 | """ 190 | Init 191 | Write value: 42.42, type: FromFormatSpecifier, valueType: System.Double, spec: 'g', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 42.42; 192 | Finalize 193 | """ 194 | } 195 | 196 | test "float64 hole only %G" { 197 | testEqual 198 | (testprintf "%G" 42.42) 199 | """ 200 | Init 201 | Write value: 42.42, type: FromFormatSpecifier, valueType: System.Double, spec: 'G', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 42.42; 202 | Finalize 203 | """ 204 | } 205 | 206 | test "float32 hole only %f" { 207 | testEqual 208 | (testprintf "%f" 42.0f) 209 | """ 210 | Init 211 | Write value: 42.0f, type: FromFormatSpecifier, valueType: System.Single, spec: 'f', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 42.000000; 212 | Finalize 213 | """ 214 | } 215 | 216 | test "Decimal hole only" { 217 | testEqual 218 | (testprintf "%M" 123456789.123456789M) 219 | """ 220 | Init 221 | Write value: 123456789.123456789M, type: FromFormatSpecifier, valueType: System.Decimal, spec: 'M', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 123456789.123456789; 222 | Finalize 223 | """ 224 | } 225 | 226 | test "F# format hole only" { 227 | testEqual 228 | (testprintf "%A" (Some true)) 229 | """ 230 | Init 231 | Write value: Some true, type: FromFormatSpecifier, valueType: Microsoft.FSharp.Core.FSharpOption`1[[System.Boolean]], spec: 'A', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: Some true; 232 | Finalize 233 | """ 234 | } 235 | 236 | test "ToString hole only" { 237 | testEqual 238 | (testprintf "%O" (MyToStringSaysHi())) 239 | """ 240 | Init 241 | Write value: Hi, type: FromFormatSpecifier, valueType: BlackFox.MasterOfFoo.Tests.InternalRepresentationTests+MyToStringSaysHi, spec: 'O', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: Hi; 242 | Finalize 243 | """ 244 | } 245 | 246 | test "IFormattable hole only" { 247 | testEqual 248 | (testprintf $"{MyFormatable():HelloWorld}") 249 | """ 250 | Init 251 | Write value: MyFormatable, type: FromFormatSpecifier, valueType: System.Object, spec: 'P', Precision=-, Width=-, Flags=None, dotnetFormat: 'HelloWorld', starWidth: , starPrecision: , AsPrintF: MyFormatable(HelloWorld); 252 | Finalize 253 | """ 254 | } 255 | 256 | test "IFormattable + unusual characters hole only" { 257 | testEqual 258 | (testprintf $"{MyFormatable():``Hello World``}") 259 | """ 260 | Init 261 | Write value: MyFormatable, type: FromFormatSpecifier, valueType: System.Object, spec: 'P', Precision=-, Width=-, Flags=None, dotnetFormat: 'Hello World', starWidth: , starPrecision: , AsPrintF: MyFormatable(Hello World); 262 | Finalize 263 | """ 264 | } 265 | 266 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 267 | // Complex case mixing holes and text 268 | 269 | test "hole and text" { 270 | testEqual 271 | (testprintf "Hello%sWorld" "Foo") 272 | """ 273 | Init 274 | Write value: "Hello", type: Direct, valueType: System.String, spec: , starWidth: , starPrecision: , AsPrintF: Hello; 275 | Write value: "Foo", type: FromFormatSpecifier, valueType: System.String, spec: 's', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: Foo; 276 | Write value: "World", type: Direct, valueType: System.String, spec: , starWidth: , starPrecision: , AsPrintF: World; 277 | Finalize 278 | """ 279 | } 280 | 281 | test "multiple holes and text" { 282 | testEqual 283 | (testprintf "Hello%sWorld%i" "Foo" 42) 284 | """ 285 | Init 286 | Write value: "Hello", type: Direct, valueType: System.String, spec: , starWidth: , starPrecision: , AsPrintF: Hello; 287 | Write value: "Foo", type: FromFormatSpecifier, valueType: System.String, spec: 's', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: Foo; 288 | Write value: "World", type: Direct, valueType: System.String, spec: , starWidth: , starPrecision: , AsPrintF: World; 289 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'i', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 42; 290 | Finalize 291 | """ 292 | } 293 | 294 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 295 | // Integer options 296 | 297 | test "plus sign" { 298 | testEqual 299 | (testprintf "%+i" 42) 300 | """ 301 | Init 302 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'i', Precision=-, Width=-, Flags=PlusForPositives, starWidth: , starPrecision: , AsPrintF: +42; 303 | Finalize 304 | """ 305 | } 306 | 307 | test "blank plus sign" { 308 | testEqual 309 | (testprintf "% i" 42) 310 | """ 311 | Init 312 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'i', Precision=-, Width=-, Flags=SpaceForPositives, starWidth: , starPrecision: , AsPrintF: 42; 313 | Finalize 314 | """ 315 | } 316 | 317 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 318 | // Alignment 319 | 320 | test "string alignment" { 321 | testEqual 322 | (testprintf "%5s" "Foo") 323 | """ 324 | Init 325 | Write value: "Foo", type: FromFormatSpecifier, valueType: System.String, spec: 's', Precision=-, Width=5, Flags=None, starWidth: , starPrecision: , AsPrintF: Foo; 326 | Finalize 327 | """ 328 | } 329 | 330 | test "int alignment" { 331 | testEqual 332 | (testprintf "%5i" 42) 333 | """ 334 | Init 335 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'i', Precision=-, Width=5, Flags=None, starWidth: , starPrecision: , AsPrintF: 42; 336 | Finalize 337 | """ 338 | } 339 | 340 | test "int pad with 0" { 341 | testEqual 342 | (testprintf "%05i" 42) 343 | """ 344 | Init 345 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'i', Precision=-, Width=5, Flags=PadWithZeros, starWidth: , starPrecision: , AsPrintF: 00042; 346 | Finalize 347 | """ 348 | } 349 | 350 | test "string left alignment" { 351 | testEqual 352 | (testprintf "%-5s" "Foo") 353 | """ 354 | Init 355 | Write value: "Foo", type: FromFormatSpecifier, valueType: System.String, spec: 's', Precision=-, Width=5, Flags=LeftJustify, starWidth: , starPrecision: , AsPrintF: Foo ; 356 | Finalize 357 | """ 358 | } 359 | 360 | test "int left alignment" { 361 | testEqual 362 | (testprintf "%-5i" 42) 363 | """ 364 | Init 365 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'i', Precision=-, Width=5, Flags=LeftJustify, starWidth: , starPrecision: , AsPrintF: 42 ; 366 | Finalize 367 | """ 368 | } 369 | 370 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 371 | // Star alignment 372 | 373 | test "string star alignment" { 374 | testEqual 375 | (testprintf "%*s" 5 "Foo") 376 | """ 377 | Init 378 | Write value: "Foo", type: FromFormatSpecifier, valueType: System.String, spec: 's', Precision=-, Width=*, Flags=None, starWidth: 5, starPrecision: , AsPrintF: Foo; 379 | Finalize 380 | """ 381 | } 382 | 383 | test "int star alignment" { 384 | testEqual 385 | (testprintf "%*i" 5 42) 386 | """ 387 | Init 388 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'i', Precision=-, Width=*, Flags=None, starWidth: 5, starPrecision: , AsPrintF: 42; 389 | Finalize 390 | """ 391 | } 392 | 393 | test "string left star alignment" { 394 | testEqual 395 | (testprintf "%-*s" 5 "Foo") 396 | """ 397 | Init 398 | Write value: "Foo", type: FromFormatSpecifier, valueType: System.String, spec: 's', Precision=-, Width=*, Flags=LeftJustify, starWidth: 5, starPrecision: , AsPrintF: Foo ; 399 | Finalize 400 | """ 401 | } 402 | 403 | test "int left star alignment" { 404 | testEqual 405 | (testprintf "%-*i" 5 42) 406 | """ 407 | Init 408 | Write value: 42, type: FromFormatSpecifier, valueType: System.Int32, spec: 'i', Precision=-, Width=*, Flags=LeftJustify, starWidth: 5, starPrecision: , AsPrintF: 42 ; 409 | Finalize 410 | """ 411 | } 412 | 413 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 414 | // Interpolation 415 | 416 | test "interpolated-explicit string hole only" { 417 | let value = "Foo" 418 | testEqual 419 | (testprintf $"%s{value}") 420 | """ 421 | Init 422 | Write value: "Foo", type: FromFormatSpecifier, valueType: System.Object, spec: 's', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: Foo; 423 | Finalize 424 | """ 425 | } 426 | 427 | test "interpolated-implicit string hole only" { 428 | let value = "Foo" 429 | testEqual 430 | (testprintf $"{value}") 431 | """ 432 | Init 433 | Write value: "Foo", type: FromFormatSpecifier, valueType: System.Object, spec: 'P', Precision=-, Width=-, Flags=None, dotnetFormat: '', starWidth: , starPrecision: , AsPrintF: Foo; 434 | Finalize 435 | """ 436 | } 437 | 438 | test "interpolated-embeded string hole only" { 439 | testEqual 440 | (testprintf $"""{"embedded string literal"}""") 441 | """ 442 | Init 443 | Write value: "embedded string literal", type: FromFormatSpecifier, valueType: System.Object, spec: 'P', Precision=-, Width=-, Flags=None, dotnetFormat: '', starWidth: , starPrecision: , AsPrintF: embedded string literal; 444 | Finalize 445 | """ 446 | } 447 | 448 | test "interpolated-explicit int hole only" { 449 | let value = 42 450 | testEqual 451 | (testprintf $"%i{value}") 452 | """ 453 | Init 454 | Write value: 42, type: FromFormatSpecifier, valueType: System.Object, spec: 'i', Precision=-, Width=-, Flags=None, starWidth: , starPrecision: , AsPrintF: 42; 455 | Finalize 456 | """ 457 | } 458 | 459 | test "interpolated-implicit int hole only" { 460 | let value = 42 461 | testEqual 462 | (testprintf $"{value}") 463 | """ 464 | Init 465 | Write value: 42, type: FromFormatSpecifier, valueType: System.Object, spec: 'P', Precision=-, Width=-, Flags=None, dotnetFormat: '', starWidth: , starPrecision: , AsPrintF: 42; 466 | Finalize 467 | """ 468 | } 469 | 470 | test "interpolated-explicit float hole F# format only" { 471 | testEqual 472 | (testprintf $"%0.3f{System.Math.PI}") 473 | """ 474 | Init 475 | Write value: 3.141592654, type: FromFormatSpecifier, valueType: System.Object, spec: 'f', Precision=3, Width=-, Flags=PadWithZeros, starWidth: , starPrecision: , AsPrintF: 3.142; 476 | Finalize 477 | """ 478 | } 479 | 480 | test "interpolated-implicit float hole dotnet format only" { 481 | testEqual 482 | (testprintf $"{System.Math.PI:N3}") 483 | """ 484 | Init 485 | Write value: 3.141592654, type: FromFormatSpecifier, valueType: System.Object, spec: 'P', Precision=-, Width=-, Flags=None, dotnetFormat: 'N3', starWidth: , starPrecision: , AsPrintF: 3.142; 486 | Finalize 487 | """ 488 | } 489 | ] 490 | 491 | [] 492 | let test = testList "Internal Representation Tests" tests 493 | -------------------------------------------------------------------------------- /paket.lock: -------------------------------------------------------------------------------- 1 | STORAGE: NONE 2 | RESTRICTION: || (== net461) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0) 3 | NUGET 4 | remote: https://api.nuget.org/v3/index.json 5 | Expecto (9.0.4) 6 | FSharp.Core (>= 4.6) 7 | Mono.Cecil (>= 0.11.3) 8 | FSharp.Core (8.0.400) 9 | Microsoft.NETCore.Platforms (7.0.4) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (&& (== net5.0) (== netstandard2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (< netcoreapp3.0)) (&& (== net8.0) (== netstandard2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netcoreapp3.0)) (== netcoreapp2.0) (&& (== netstandard2.0) (>= netcoreapp2.0)) (&& (== netstandard2.0) (>= netcoreapp2.1)) 10 | Microsoft.Win32.Registry (5.0) - restriction: || (&& (== net461) (< net451) (>= netstandard2.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0) 11 | System.Buffers (>= 4.5.1) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (== netcoreapp2.0)) (&& (== net461) (>= monoandroid) (< netstandard1.3)) (&& (== net461) (>= monotouch)) (&& (== net461) (< net46) (>= netstandard2.0)) (&& (== net461) (>= xamarinios)) (&& (== net461) (>= xamarinmac)) (&& (== net461) (>= xamarintvos)) (&& (== net461) (>= xamarinwatchos)) (&& (== net5.0) (>= monoandroid) (< netstandard1.3)) (&& (== net5.0) (>= monotouch)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (>= xamarinios)) (&& (== net5.0) (>= xamarinmac)) (&& (== net5.0) (>= xamarintvos)) (&& (== net5.0) (>= xamarinwatchos)) (&& (== net8.0) (>= monoandroid) (< netstandard1.3)) (&& (== net8.0) (>= monotouch)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (>= xamarinios)) (&& (== net8.0) (>= xamarinmac)) (&& (== net8.0) (>= xamarintvos)) (&& (== net8.0) (>= xamarinwatchos)) (&& (== netcoreapp2.0) (>= monoandroid) (< netstandard1.3)) (&& (== netcoreapp2.0) (>= monotouch)) (&& (== netcoreapp2.0) (>= xamarinios)) (&& (== netcoreapp2.0) (>= xamarinmac)) (&& (== netcoreapp2.0) (>= xamarintvos)) (&& (== netcoreapp2.0) (>= xamarinwatchos)) (== netstandard2.0) 12 | System.Memory (>= 4.5.4) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (< net46) (>= netstandard2.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= uap10.1)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (>= uap10.1)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (>= uap10.1)) (== netcoreapp2.0) (== netstandard2.0) 13 | System.Security.AccessControl (>= 5.0) 14 | System.Security.Principal.Windows (>= 5.0) 15 | Mono.Cecil (0.11.5) 16 | runtime.native.System.Data.SqlClient.sni (4.7) - restriction: || (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0) 17 | runtime.win-arm64.runtime.native.System.Data.SqlClient.sni (>= 4.4) 18 | runtime.win-x64.runtime.native.System.Data.SqlClient.sni (>= 4.4) 19 | runtime.win-x86.runtime.native.System.Data.SqlClient.sni (>= 4.4) 20 | runtime.win-arm64.runtime.native.System.Data.SqlClient.sni (4.4) - restriction: || (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0) 21 | runtime.win-x64.runtime.native.System.Data.SqlClient.sni (4.4) - restriction: || (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0) 22 | runtime.win-x86.runtime.native.System.Data.SqlClient.sni (4.4) - restriction: || (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0) 23 | System.Buffers (4.5.1) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (== netcoreapp2.0)) (&& (== net461) (< net451) (>= netstandard2.0)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.0)) (== netstandard2.0) 24 | System.Data.SqlClient (4.8.6) 25 | Microsoft.Win32.Registry (>= 4.7) - restriction: || (&& (== net461) (< net451) (>= netstandard2.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0) 26 | runtime.native.System.Data.SqlClient.sni (>= 4.7) - restriction: || (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0) 27 | System.Buffers (>= 4.5.1) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (== netcoreapp2.0)) (&& (== net461) (< net451) (>= netstandard2.0)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.0)) (== netstandard2.0) 28 | System.Diagnostics.DiagnosticSource (>= 4.7) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= uap10.1)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (< netcoreapp3.1)) (&& (== net5.0) (< netstandard2.0)) (&& (== net5.0) (>= uap10.1)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netcoreapp3.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= uap10.1)) (== netcoreapp2.0) (== netstandard2.0) 29 | System.Memory (>= 4.5.4) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (< net451) (>= netstandard2.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= uap10.1)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (>= uap10.1)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (>= uap10.1)) (== netcoreapp2.0) (== netstandard2.0) 30 | System.Security.Principal.Windows (>= 4.7) - restriction: || (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0) 31 | System.Text.Encoding.CodePages (>= 4.7) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= uap10.1)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (< netcoreapp3.1)) (&& (== net5.0) (< netstandard2.0)) (&& (== net5.0) (>= uap10.1)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netcoreapp3.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= uap10.1)) (== netcoreapp2.0) (== netstandard2.0) 32 | System.Diagnostics.DiagnosticSource (8.0.1) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= uap10.1)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (< netcoreapp3.1)) (&& (== net5.0) (< netstandard2.0)) (&& (== net5.0) (>= uap10.1)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netcoreapp3.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= uap10.1)) (== netcoreapp2.0) (== netstandard2.0) 33 | System.Memory (>= 4.5.5) - restriction: || (&& (== net461) (== net8.0)) (&& (== net461) (>= net462)) (&& (== net461) (>= netstandard2.0)) (== net5.0) (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (== netcoreapp2.0) (== netstandard2.0) 34 | System.Runtime.CompilerServices.Unsafe (>= 6.0) - restriction: || (&& (== net461) (== net8.0)) (&& (== net461) (>= net462)) (&& (== net461) (>= net6.0)) (&& (== net461) (>= netstandard2.0)) (== net5.0) (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (&& (== net8.0) (< net7.0)) (== netcoreapp2.0) (== netstandard2.0) 35 | System.Memory (4.5.5) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (< net451) (>= net462)) (&& (== net461) (< net451) (>= netstandard2.0)) (&& (== net461) (>= net462) (>= uap10.1)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netstandard2.0) (>= uap10.1)) (&& (== net5.0) (>= net462)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (< netcoreapp3.1)) (&& (== net5.0) (< netstandard2.0)) (&& (== net5.0) (>= uap10.1)) (&& (== net8.0) (>= net462)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netcoreapp3.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= uap10.1)) (== netcoreapp2.0) (== netstandard2.0) 36 | System.Buffers (>= 4.5.1) - restriction: || (== net461) (&& (== net5.0) (>= monotouch)) (&& (== net5.0) (>= net461)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netstandard1.1)) (&& (== net5.0) (< netstandard2.0)) (&& (== net5.0) (>= xamarinios)) (&& (== net5.0) (>= xamarinmac)) (&& (== net5.0) (>= xamarintvos)) (&& (== net5.0) (>= xamarinwatchos)) (&& (== net8.0) (>= monotouch)) (&& (== net8.0) (>= net461)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netstandard1.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= xamarinios)) (&& (== net8.0) (>= xamarinmac)) (&& (== net8.0) (>= xamarintvos)) (&& (== net8.0) (>= xamarinwatchos)) (&& (== netcoreapp2.0) (>= monotouch)) (&& (== netcoreapp2.0) (>= net461)) (&& (== netcoreapp2.0) (< netstandard1.1)) (&& (== netcoreapp2.0) (< netstandard2.0)) (&& (== netcoreapp2.0) (>= xamarinios)) (&& (== netcoreapp2.0) (>= xamarinmac)) (&& (== netcoreapp2.0) (>= xamarintvos)) (&& (== netcoreapp2.0) (>= xamarinwatchos)) (== netstandard2.0) 37 | System.Numerics.Vectors (>= 4.4) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (== netcoreapp2.0)) (&& (== net461) (< net45) (>= netstandard2.0)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.0)) (== netstandard2.0) 38 | System.Runtime.CompilerServices.Unsafe (>= 4.5.3) - restriction: || (== net461) (&& (== net5.0) (>= monotouch)) (&& (== net5.0) (>= net461)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (< netstandard1.1)) (&& (== net5.0) (< netstandard2.0)) (&& (== net5.0) (>= uap10.1)) (&& (== net5.0) (>= xamarinios)) (&& (== net5.0) (>= xamarinmac)) (&& (== net5.0) (>= xamarintvos)) (&& (== net5.0) (>= xamarinwatchos)) (&& (== net8.0) (>= monotouch)) (&& (== net8.0) (>= net461)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netstandard1.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= uap10.1)) (&& (== net8.0) (>= xamarinios)) (&& (== net8.0) (>= xamarinmac)) (&& (== net8.0) (>= xamarintvos)) (&& (== net8.0) (>= xamarinwatchos)) (== netcoreapp2.0) (== netstandard2.0) 39 | System.Numerics.Vectors (4.5) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (== netcoreapp2.0)) (&& (== net461) (< net45) (>= netstandard2.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netstandard2.0) (>= uap10.1)) (&& (== net5.0) (>= net462)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netstandard2.0)) (&& (== net5.0) (>= uap10.1)) (&& (== net8.0) (>= net462)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= uap10.1)) (== netstandard2.0) 40 | System.Runtime.CompilerServices.Unsafe (6.0) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (< net451) (>= net462)) (&& (== net461) (< net451) (>= netstandard2.0)) (&& (== net461) (>= net462) (>= uap10.1)) (&& (== net461) (>= net6.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netstandard2.0) (>= uap10.1)) (&& (== net5.0) (>= net462)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (< netcoreapp3.1)) (&& (== net5.0) (< netstandard2.0)) (&& (== net5.0) (>= uap10.1)) (&& (== net8.0) (>= net462)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netcoreapp3.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= uap10.1)) (== netcoreapp2.0) (== netstandard2.0) 41 | System.Security.AccessControl (6.0.1) - restriction: || (&& (== net461) (>= monoandroid) (< netstandard1.3) (>= netstandard2.0)) (&& (== net461) (>= monotouch) (>= netstandard2.0)) (&& (== net461) (< net451) (>= netstandard2.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (&& (== net461) (>= netstandard2.0) (>= uap10.1)) (&& (== net461) (>= netstandard2.0) (>= xamarintvos)) (&& (== net461) (>= netstandard2.0) (>= xamarinwatchos)) (&& (== net461) (>= xamarinios)) (&& (== net461) (>= xamarinmac)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0) 42 | System.Security.Principal.Windows (>= 5.0) - restriction: || (== net461) (== net5.0) (&& (== net8.0) (>= net461)) (&& (== net8.0) (< net6.0)) (== netcoreapp2.0) (== netstandard2.0) 43 | System.Security.Principal.Windows (5.0) - restriction: || (&& (== net461) (>= monoandroid) (< netstandard1.3) (>= netstandard2.0)) (&& (== net461) (>= monotouch) (>= netstandard2.0)) (&& (== net461) (< net451) (>= netstandard2.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= netcoreapp3.1)) (&& (== net461) (>= netstandard2.0) (>= uap10.1)) (&& (== net461) (>= netstandard2.0) (>= xamarintvos)) (&& (== net461) (>= netstandard2.0) (>= xamarinwatchos)) (&& (== net461) (>= xamarinios)) (&& (== net461) (>= xamarinmac)) (== net5.0) (== net8.0) (== netcoreapp2.0) (== netstandard2.0) 44 | Microsoft.NETCore.Platforms (>= 5.0) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net5.0) (== netstandard2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (< netcoreapp3.0)) (&& (== net8.0) (== netstandard2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netcoreapp3.0)) (== netcoreapp2.0) (&& (== netstandard2.0) (>= netcoreapp2.0)) (&& (== netstandard2.0) (>= netcoreapp2.1)) 45 | System.Text.Encoding.CodePages (8.0) - restriction: || (&& (== net461) (== net5.0)) (&& (== net461) (== net8.0)) (&& (== net461) (< net451)) (&& (== net461) (>= netcoreapp2.0)) (&& (== net461) (>= netcoreapp2.1)) (&& (== net461) (>= uap10.1)) (&& (== net5.0) (< netcoreapp2.0)) (&& (== net5.0) (< netcoreapp2.1)) (&& (== net5.0) (< netcoreapp3.1)) (&& (== net5.0) (< netstandard2.0)) (&& (== net5.0) (>= uap10.1)) (&& (== net8.0) (< netcoreapp2.0)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netcoreapp3.1)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= uap10.1)) (== netcoreapp2.0) (== netstandard2.0) 46 | System.Memory (>= 4.5.5) - restriction: || (&& (== net461) (== net8.0)) (&& (== net461) (>= net462)) (&& (== net461) (>= netstandard2.0)) (== net5.0) (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (== netcoreapp2.0) (== netstandard2.0) 47 | System.Runtime.CompilerServices.Unsafe (>= 6.0) - restriction: || (&& (== net461) (== net8.0)) (&& (== net461) (>= net462)) (&& (== net461) (>= net6.0)) (&& (== net461) (>= netstandard2.0)) (== net5.0) (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (&& (== net8.0) (< net7.0)) (== netcoreapp2.0) (== netstandard2.0) 48 | 49 | GROUP build 50 | STORAGE: NONE 51 | RESTRICTION: == net8.0 52 | NUGET 53 | remote: https://api.nuget.org/v3/index.json 54 | BlackFox.Fake.BuildTask (0.1.3) 55 | Fake.Core.Target (>= 5.1) 56 | FSharp.Core (>= 4.3.4) 57 | BlackFox.VsWhere (1.1) 58 | FSharp.Core (>= 4.2.3) 59 | Microsoft.Win32.Registry (>= 4.7) 60 | Fake.Api.GitHub (5.20.4) 61 | FSharp.Core (>= 4.7.2) 62 | Octokit (>= 0.48) 63 | Fake.BuildServer.GitHubActions (5.20.4) 64 | Fake.Core.Environment (>= 5.20.4) 65 | Fake.Core.Trace (>= 5.20.4) 66 | Fake.IO.FileSystem (>= 5.20.4) 67 | FSharp.Core (>= 4.7.2) 68 | Fake.Core.CommandLineParsing (5.20.4) 69 | FParsec (>= 1.1.1) 70 | FSharp.Core (>= 4.7.2) 71 | Fake.Core.Context (5.20.4) 72 | FSharp.Core (>= 4.7.2) 73 | Fake.Core.Environment (5.20.4) 74 | FSharp.Core (>= 4.7.2) 75 | Fake.Core.FakeVar (5.20.4) 76 | Fake.Core.Context (>= 5.20.4) 77 | FSharp.Core (>= 4.7.2) 78 | Fake.Core.Process (5.20.4) 79 | Fake.Core.Environment (>= 5.20.4) 80 | Fake.Core.FakeVar (>= 5.20.4) 81 | Fake.Core.String (>= 5.20.4) 82 | Fake.Core.Trace (>= 5.20.4) 83 | Fake.IO.FileSystem (>= 5.20.4) 84 | FSharp.Core (>= 4.7.2) 85 | System.Collections.Immutable (>= 1.7.1) 86 | Fake.Core.ReleaseNotes (5.20.4) 87 | Fake.Core.SemVer (>= 5.20.4) 88 | Fake.Core.String (>= 5.20.4) 89 | FSharp.Core (>= 4.7.2) 90 | Fake.Core.SemVer (5.20.4) 91 | FSharp.Core (>= 4.7.2) 92 | Fake.Core.String (5.20.4) 93 | FSharp.Core (>= 4.7.2) 94 | Fake.Core.Target (5.20.4) 95 | Fake.Core.CommandLineParsing (>= 5.20.4) 96 | Fake.Core.Context (>= 5.20.4) 97 | Fake.Core.Environment (>= 5.20.4) 98 | Fake.Core.FakeVar (>= 5.20.4) 99 | Fake.Core.Process (>= 5.20.4) 100 | Fake.Core.String (>= 5.20.4) 101 | Fake.Core.Trace (>= 5.20.4) 102 | FSharp.Control.Reactive (>= 4.4.2) 103 | FSharp.Core (>= 4.7.2) 104 | Fake.Core.Tasks (5.20.4) 105 | Fake.Core.Trace (>= 5.20.4) 106 | FSharp.Core (>= 4.7.2) 107 | Fake.Core.Trace (5.20.4) 108 | Fake.Core.Environment (>= 5.20.4) 109 | Fake.Core.FakeVar (>= 5.20.4) 110 | FSharp.Core (>= 4.7.2) 111 | Fake.Core.UserInput (5.20.4) 112 | FSharp.Core (>= 4.7.2) 113 | Fake.Core.Xml (5.20.4) 114 | Fake.Core.String (>= 5.20.4) 115 | FSharp.Core (>= 4.7.2) 116 | Fake.DotNet.AssemblyInfoFile (5.20.4) 117 | Fake.Core.Environment (>= 5.20.4) 118 | Fake.Core.String (>= 5.20.4) 119 | Fake.Core.Trace (>= 5.20.4) 120 | Fake.IO.FileSystem (>= 5.20.4) 121 | FSharp.Core (>= 4.7.2) 122 | Fake.DotNet.Cli (5.20.4) 123 | Fake.Core.Environment (>= 5.20.4) 124 | Fake.Core.Process (>= 5.20.4) 125 | Fake.Core.String (>= 5.20.4) 126 | Fake.Core.Trace (>= 5.20.4) 127 | Fake.DotNet.MSBuild (>= 5.20.4) 128 | Fake.DotNet.NuGet (>= 5.20.4) 129 | Fake.IO.FileSystem (>= 5.20.4) 130 | FSharp.Core (>= 4.7.2) 131 | Mono.Posix.NETStandard (>= 1.0) 132 | Newtonsoft.Json (>= 12.0.3) 133 | Fake.DotNet.MSBuild (5.20.4) 134 | BlackFox.VsWhere (>= 1.1) 135 | Fake.Core.Environment (>= 5.20.4) 136 | Fake.Core.Process (>= 5.20.4) 137 | Fake.Core.String (>= 5.20.4) 138 | Fake.Core.Trace (>= 5.20.4) 139 | Fake.IO.FileSystem (>= 5.20.4) 140 | FSharp.Core (>= 4.7.2) 141 | MSBuild.StructuredLogger (>= 2.1.176) 142 | Fake.DotNet.NuGet (5.20.4) 143 | Fake.Core.Environment (>= 5.20.4) 144 | Fake.Core.Process (>= 5.20.4) 145 | Fake.Core.SemVer (>= 5.20.4) 146 | Fake.Core.String (>= 5.20.4) 147 | Fake.Core.Tasks (>= 5.20.4) 148 | Fake.Core.Trace (>= 5.20.4) 149 | Fake.Core.Xml (>= 5.20.4) 150 | Fake.IO.FileSystem (>= 5.20.4) 151 | Fake.Net.Http (>= 5.20.4) 152 | FSharp.Core (>= 4.7.2) 153 | Newtonsoft.Json (>= 12.0.3) 154 | NuGet.Protocol (>= 5.6) 155 | Fake.DotNet.Paket (5.20.4) 156 | Fake.Core.Process (>= 5.20.4) 157 | Fake.Core.String (>= 5.20.4) 158 | Fake.Core.Trace (>= 5.20.4) 159 | Fake.DotNet.Cli (>= 5.20.4) 160 | Fake.IO.FileSystem (>= 5.20.4) 161 | FSharp.Core (>= 4.7.2) 162 | Fake.DotNet.Testing.Expecto (5.20.4) 163 | Fake.Core.Process (>= 5.20.4) 164 | Fake.Core.String (>= 5.20.4) 165 | Fake.Core.Trace (>= 5.20.4) 166 | Fake.IO.FileSystem (>= 5.20.4) 167 | Fake.Testing.Common (>= 5.20.4) 168 | FSharp.Core (>= 4.7.2) 169 | Fake.IO.FileSystem (5.20.4) 170 | Fake.Core.String (>= 5.20.4) 171 | FSharp.Core (>= 4.7.2) 172 | Fake.IO.Zip (5.20.4) 173 | Fake.Core.String (>= 5.20.4) 174 | Fake.IO.FileSystem (>= 5.20.4) 175 | FSharp.Core (>= 4.7.2) 176 | Fake.Net.Http (5.20.4) 177 | Fake.Core.Trace (>= 5.20.4) 178 | FSharp.Core (>= 4.7.2) 179 | Fake.Testing.Common (5.20.4) 180 | Fake.Core.Trace (>= 5.20.4) 181 | FSharp.Core (>= 4.7.2) 182 | Fake.Tools.Git (5.20.4) 183 | Fake.Core.Environment (>= 5.20.4) 184 | Fake.Core.Process (>= 5.20.4) 185 | Fake.Core.SemVer (>= 5.20.4) 186 | Fake.Core.String (>= 5.20.4) 187 | Fake.Core.Trace (>= 5.20.4) 188 | Fake.IO.FileSystem (>= 5.20.4) 189 | FSharp.Core (>= 4.7.2) 190 | FParsec (1.1.1) 191 | FSharp.Core (>= 4.3.4) 192 | FSharp.Control.Reactive (5.0.5) 193 | FSharp.Core (>= 4.7.2) 194 | System.Reactive (>= 5.0 < 6.0) 195 | FSharp.Core (5.0.2) 196 | Microsoft.Build.Framework (17.11.4) 197 | Microsoft.Build.Utilities.Core (17.11.4) 198 | Microsoft.Build.Framework (>= 17.11.4) 199 | Microsoft.NET.StringTools (>= 17.11.4) 200 | System.Collections.Immutable (>= 8.0) 201 | System.Configuration.ConfigurationManager (>= 8.0) 202 | Microsoft.NET.StringTools (17.11.4) 203 | Microsoft.Win32.Registry (5.0) 204 | System.Security.AccessControl (>= 5.0) 205 | System.Security.Principal.Windows (>= 5.0) 206 | Mono.Posix.NETStandard (1.0) 207 | MSBuild.StructuredLogger (2.2.337) 208 | Microsoft.Build.Framework (>= 17.5) 209 | Microsoft.Build.Utilities.Core (>= 17.5) 210 | Newtonsoft.Json (13.0.3) 211 | NuGet.Common (6.11) 212 | NuGet.Frameworks (>= 6.11) 213 | NuGet.Configuration (6.11) 214 | NuGet.Common (>= 6.11) 215 | System.Security.Cryptography.ProtectedData (>= 4.4) 216 | NuGet.Frameworks (6.11) 217 | NuGet.Packaging (6.11) 218 | Newtonsoft.Json (>= 13.0.3) 219 | NuGet.Configuration (>= 6.11) 220 | NuGet.Versioning (>= 6.11) 221 | System.Security.Cryptography.Pkcs (>= 6.0.4) 222 | NuGet.Protocol (6.11) 223 | NuGet.Packaging (>= 6.11) 224 | NuGet.Versioning (6.11) 225 | Octokit (0.48) 226 | System.Collections.Immutable (8.0) 227 | System.Configuration.ConfigurationManager (8.0) 228 | System.Diagnostics.EventLog (>= 8.0) 229 | System.Security.Cryptography.ProtectedData (>= 8.0) 230 | System.Diagnostics.EventLog (8.0) 231 | System.Formats.Asn1 (8.0.1) 232 | System.Reactive (5.0) 233 | System.Security.AccessControl (6.0.1) 234 | System.Security.Cryptography.Pkcs (8.0) 235 | System.Formats.Asn1 (>= 8.0) 236 | System.Security.Cryptography.ProtectedData (8.0) 237 | System.Security.Principal.Windows (5.0) 238 | -------------------------------------------------------------------------------- /.paket/Paket.Restore.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 8 | 9 | $(MSBuildVersion) 10 | 15.0.0 11 | false 12 | true 13 | 14 | true 15 | $(MSBuildThisFileDirectory) 16 | $(MSBuildThisFileDirectory)..\ 17 | $(PaketRootPath)paket-files\paket.restore.cached 18 | $(PaketRootPath)paket.lock 19 | classic 20 | proj 21 | assembly 22 | native 23 | /Library/Frameworks/Mono.framework/Commands/mono 24 | mono 25 | 26 | 27 | $(PaketRootPath)paket.bootstrapper.exe 28 | $(PaketToolsPath)paket.bootstrapper.exe 29 | $([System.IO.Path]::GetDirectoryName("$(PaketBootStrapperExePath)"))\ 30 | 31 | "$(PaketBootStrapperExePath)" 32 | $(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)" 33 | 34 | 35 | 36 | 37 | true 38 | true 39 | 40 | 41 | True 42 | 43 | 44 | False 45 | 46 | $(BaseIntermediateOutputPath.TrimEnd('\').TrimEnd('\/')) 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | $(PaketRootPath)paket 56 | $(PaketToolsPath)paket 57 | 58 | 59 | 60 | 61 | 62 | $(PaketRootPath)paket.exe 63 | $(PaketToolsPath)paket.exe 64 | 65 | 66 | 67 | 68 | 69 | <_DotnetToolsJson Condition="Exists('$(PaketRootPath)/.config/dotnet-tools.json')">$([System.IO.File]::ReadAllText("$(PaketRootPath)/.config/dotnet-tools.json")) 70 | <_ConfigContainsPaket Condition=" '$(_DotnetToolsJson)' != ''">$(_DotnetToolsJson.Contains('"paket"')) 71 | <_ConfigContainsPaket Condition=" '$(_ConfigContainsPaket)' == ''">false 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | <_PaketCommand>dotnet paket 83 | 84 | 85 | 86 | 87 | 88 | $(PaketToolsPath)paket 89 | $(PaketBootStrapperExeDir)paket 90 | 91 | 92 | paket 93 | 94 | 95 | 96 | 97 | <_PaketExeExtension>$([System.IO.Path]::GetExtension("$(PaketExePath)")) 98 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(_PaketExeExtension)' == '.dll' ">dotnet "$(PaketExePath)" 99 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(OS)' != 'Windows_NT' AND '$(_PaketExeExtension)' == '.exe' ">$(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" 100 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' ">"$(PaketExePath)" 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | true 122 | $(NoWarn);NU1603;NU1604;NU1605;NU1608 123 | false 124 | true 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)')) 134 | 135 | 136 | 137 | 138 | 139 | 141 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[0].Replace(`"`, ``).Replace(` `, ``)) 142 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[1].Replace(`"`, ``).Replace(` `, ``)) 143 | 144 | 145 | 146 | 147 | %(PaketRestoreCachedKeyValue.Value) 148 | %(PaketRestoreCachedKeyValue.Value) 149 | 150 | 151 | 152 | 153 | true 154 | false 155 | true 156 | 157 | 158 | 162 | 163 | true 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | $(PaketIntermediateOutputPath)\$(MSBuildProjectFile).paket.references.cached 183 | 184 | $(MSBuildProjectFullPath).paket.references 185 | 186 | $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references 187 | 188 | $(MSBuildProjectDirectory)\paket.references 189 | 190 | false 191 | true 192 | true 193 | references-file-or-cache-not-found 194 | 195 | 196 | 197 | 198 | $([System.IO.File]::ReadAllText('$(PaketReferencesCachedFilePath)')) 199 | $([System.IO.File]::ReadAllText('$(PaketOriginalReferencesFilePath)')) 200 | references-file 201 | false 202 | 203 | 204 | 205 | 206 | false 207 | 208 | 209 | 210 | 211 | true 212 | target-framework '$(TargetFramework)' or '$(TargetFrameworks)' files @(PaketResolvedFilePaths) 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | false 224 | true 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',').Length) 236 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0]) 237 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1]) 238 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4]) 239 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5]) 240 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[6]) 241 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[7]) 242 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[8]) 243 | 244 | 245 | %(PaketReferencesFileLinesInfo.PackageVersion) 246 | All 247 | runtime 248 | $(ExcludeAssets);contentFiles 249 | $(ExcludeAssets);build;buildMultitargeting;buildTransitive 250 | %(PaketReferencesFileLinesInfo.Aliases) 251 | true 252 | true 253 | 254 | 255 | 256 | 257 | 258 | $(PaketIntermediateOutputPath)/$(MSBuildProjectFile).paket.clitools 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[0]) 268 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[1]) 269 | 270 | 271 | %(PaketCliToolFileLinesInfo.PackageVersion) 272 | 273 | 274 | 275 | 279 | 280 | 281 | 282 | 283 | 284 | false 285 | 286 | 287 | 288 | 289 | 290 | <_NuspecFilesNewLocation Include="$(PaketIntermediateOutputPath)\$(Configuration)\*.nuspec"/> 291 | 292 | 293 | 294 | 295 | 296 | $(MSBuildProjectDirectory)/$(MSBuildProjectFile) 297 | true 298 | false 299 | true 300 | false 301 | true 302 | false 303 | true 304 | false 305 | true 306 | false 307 | true 308 | $(PaketIntermediateOutputPath)\$(Configuration) 309 | $(PaketIntermediateOutputPath) 310 | 311 | 312 | 313 | <_NuspecFiles Include="$(AdjustedNuspecOutputPath)\*.$(PackageVersion.Split(`+`)[0]).nuspec"/> 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 373 | 374 | 423 | 424 | 469 | 470 | 514 | 515 | 558 | 559 | 560 | 561 | -------------------------------------------------------------------------------- /src/BlackFox.MasterOfFoo/printf.fs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. 2 | 3 | namespace BlackFox.MasterOfFoo.Core 4 | 5 | open System 6 | open System.IO 7 | open System.Text 8 | 9 | open System.Collections.Concurrent 10 | open System.Globalization 11 | open System.Reflection 12 | 13 | open Microsoft.FSharp.Core 14 | open Microsoft.FSharp.Core.Operators 15 | open Microsoft.FSharp.Collections 16 | 17 | open LanguagePrimitives.IntrinsicOperators 18 | 19 | type objnull = obj 20 | open BlackFox.MasterOfFoo 21 | open BlackFox.MasterOfFoo.FormatFlagsHelpers 22 | 23 | [] 24 | module internal PrintfImpl = 25 | 26 | /// Basic idea of implementation: 27 | /// Every Printf.* family should returns curried function that collects arguments and then somehow prints them. 28 | /// Idea - instead of building functions on fly argument by argument we instead introduce some predefined parts and then construct functions from these parts 29 | /// Parts include: 30 | /// Plain ones: 31 | /// 1. Final pieces (1..5) - set of functions with arguments number 1..5. 32 | /// Primary characteristic - these functions produce final result of the *printf* operation 33 | /// 2. Chained pieces (1..5) - set of functions with arguments number 1..5. 34 | /// Primary characteristic - these functions doesn not produce final result by itself, instead they tailed with some another piece (chained or final). 35 | /// Plain parts correspond to simple format specifiers (that are projected to just one parameter of the function, say %d or %s). However we also have 36 | /// format specifiers that can be projected to more than one argument (i.e %a, %t or any simple format specified with * width or precision). 37 | /// For them we add special cases (both chained and final to denote that they can either return value themselves or continue with some other piece) 38 | /// These primitives allow us to construct curried functions with arbitrary signatures. 39 | /// For example: 40 | /// - function that corresponds to %s%s%s%s%s (string -> string -> string -> string -> string -> T) will be represented by one piece final 5. 41 | /// - function that has more that 5 arguments will include chained parts: %s%s%s%s%s%d%s => chained2 -> final 5 42 | /// Primary benefits: 43 | /// 1. creating specialized version of any part requires only one reflection call. This means that we can handle up to 5 simple format specifiers 44 | /// with just one reflection call 45 | /// 2. we can make combinable parts independent from particular printf implementation. Thus final result can be cached and shared. 46 | /// i.e when first call to printf "%s %s" will trigger creation of the specialization. Subsequent calls will pick existing specialization 47 | 48 | /// Used for width and precision to denote that user has specified '*' flag 49 | [] 50 | let StarValue = -1 51 | /// Used for width and precision to denote that corresponding value was omitted in format string 52 | [] 53 | let NotSpecifiedValue = -2 54 | 55 | /// Set of helpers to parse format string 56 | module private FormatString = 57 | 58 | let intFromString (s: string) (i: byref) = 59 | let mutable res = 0 60 | while (Char.IsDigit s.[i]) do 61 | let n = int s.[i] - int '0' 62 | res <- res * 10 + n 63 | i <- i + 1 64 | res 65 | 66 | let parseFlags (s: string) (i: byref) = 67 | let mutable flags = FormatFlags.None 68 | let mutable fin = false 69 | while not fin do 70 | match s.[i] with 71 | | '0' -> 72 | flags <- flags ||| FormatFlags.PadWithZeros 73 | i <- i + 1 74 | | '+' -> 75 | flags <- flags ||| FormatFlags.PlusForPositives 76 | i <- i + 1 77 | | ' ' -> 78 | flags <- flags ||| FormatFlags.SpaceForPositives 79 | i <- i + 1 80 | | '-' -> 81 | flags <- flags ||| FormatFlags.LeftJustify 82 | i <- i + 1 83 | | _ -> 84 | fin <- true 85 | flags 86 | 87 | let parseWidth (s: string) (i: byref) = 88 | if s.[i] = '*' then 89 | i <- i + 1 90 | StarValue 91 | elif Char.IsDigit s.[i] then 92 | intFromString s (&i) 93 | else 94 | NotSpecifiedValue 95 | 96 | let parsePrecision (s: string) (i: byref) = 97 | if s.[i] = '.' then 98 | if s.[i + 1] = '*' then 99 | i <- i + 2 100 | StarValue 101 | elif Char.IsDigit s.[i + 1] then 102 | i <- i + 1 103 | intFromString s (&i) 104 | else raise (ArgumentException("invalid precision value")) 105 | else 106 | NotSpecifiedValue 107 | 108 | let parseTypeChar (s: string) (i: byref) = 109 | let res = s.[i] 110 | i <- i + 1 111 | res 112 | 113 | let parseInterpolatedHoleDotNetFormat typeChar (s: string) (i: byref) = 114 | if typeChar = 'P' then 115 | if i < s.Length && s.[i] = '(' then 116 | let i2 = s.IndexOf(")", i) 117 | if i2 = -1 then 118 | ValueNone 119 | else 120 | let res = s.[i+1..i2-1] 121 | i <- i2+1 122 | ValueSome res 123 | else 124 | ValueNone 125 | else 126 | ValueNone 127 | 128 | // Skip %P() added for hole in "...%d{x}..." 129 | let skipInterpolationHole typeChar (fmt: string) (i: byref) = 130 | if typeChar <> 'P' then 131 | if i+1 < fmt.Length && fmt.[i] = '%' && fmt.[i+1] = 'P' then 132 | i <- i + 2 133 | if i+1 < fmt.Length && fmt.[i] = '(' && fmt.[i+1] = ')' then 134 | i <- i+2 135 | 136 | let findNextFormatSpecifier (s: string) (i: byref): PrintableElement = 137 | let buf = StringBuilder() 138 | let mutable fin = false 139 | while not fin do 140 | if i >= s.Length then 141 | fin <- true 142 | else 143 | let c = s.[i] 144 | if c = '%' then 145 | if i + 1 < s.Length then 146 | let mutable i2 = i+1 147 | let _ = parseFlags s &i2 148 | let w = parseWidth s &i2 149 | let p = parsePrecision s &i2 150 | let typeChar = parseTypeChar s &i2 151 | 152 | // shortcut for the simplest case 153 | // if typeChar is not % or it has star as width\precision - resort to long path 154 | if typeChar = '%' && not (w = StarValue || p = StarValue) then 155 | buf.Append('%') |> ignore 156 | i <- i2 157 | else 158 | fin <- true 159 | else 160 | raise (ArgumentException("Missing format specifier")) 161 | else 162 | buf.Append c |> ignore 163 | i <- i + 1 164 | PrintableElement(buf.ToString(), PrintableElementType.Direct) 165 | 166 | /// Type of results produced by specialization. 167 | /// 168 | /// This is a function that accepts a thunk to create PrintfEnv on demand (at the very last 169 | /// application of an argument) and returns a concrete instance of an appropriate curried printer. 170 | /// 171 | /// After all arguments are collected, specialization obtains concrete PrintfEnv from the thunk 172 | /// and uses it to output collected data. 173 | /// 174 | /// Note the arguments must be captured in an *immutable* collection. For example consider 175 | /// let f1 = printf "%d%d%d" 3 // activation captures '3' (args --> [3]) 176 | /// let f2 = f1 4 // same activation captures 4 (args --> [3;4]) 177 | /// let f3 = f1 5 // same activation captures 5 (args --> [3;5]) 178 | /// f2 7 // same activation captures 7 (args --> [3;4;7]) 179 | /// f3 8 // same activation captures 8 (args --> [3;5;8]) 180 | /// 181 | /// If we captured into an mutable array then these would interfere 182 | type PrintfInitial<'State, 'Residue, 'Result> = (unit -> PrintfEnv<'State, 'Residue, 'Result>) 183 | type PrintfFuncFactory<'Printer, 'State, 'Residue, 'Result> = 184 | delegate of objnull list * PrintfInitial<'State, 'Residue, 'Result> -> 'Printer 185 | 186 | [] 187 | let MaxArgumentsInSpecialization = 3 188 | 189 | let revToArray extra (args: 'T list) = 190 | // We've reached the end, now fill in the array, reversing steps, avoiding reallocating 191 | let n = args.Length 192 | let res = Array.zeroCreate (n+extra) 193 | let mutable j = 0 194 | for arg in args do 195 | res.[n-j-1] <- arg 196 | j <- j + 1 197 | res 198 | 199 | type Specializations<'State, 'Residue, 'Result>() = 200 | 201 | static member Final0(allSteps) = 202 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun args initial -> 203 | let env = initial() 204 | env.RunSteps(revToArray 0 args, null, allSteps) 205 | ) 206 | 207 | static member CaptureFinal1<'A>(allSteps) = 208 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun args initial -> 209 | (fun (arg1: 'A) -> 210 | let env = initial() 211 | let argArray = revToArray 1 args 212 | argArray.[argArray.Length-1] <- box arg1 213 | env.RunSteps(argArray, null, allSteps) 214 | ) 215 | ) 216 | 217 | static member CaptureFinal2<'A, 'B>(allSteps) = 218 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun args initial -> 219 | (fun (arg1: 'A) (arg2: 'B) -> 220 | let env = initial() 221 | let argArray = revToArray 2 args 222 | argArray.[argArray.Length-1] <- box arg2 223 | argArray.[argArray.Length-2] <- box arg1 224 | env.RunSteps(argArray, null, allSteps) 225 | ) 226 | ) 227 | 228 | static member CaptureFinal3<'A, 'B, 'C>(allSteps) = 229 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun args initial -> 230 | (fun (arg1: 'A) (arg2: 'B) (arg3: 'C) -> 231 | let env = initial() 232 | let argArray = revToArray 3 args 233 | argArray.[argArray.Length-1] <- box arg3 234 | argArray.[argArray.Length-2] <- box arg2 235 | argArray.[argArray.Length-3] <- box arg1 236 | env.RunSteps(argArray, null, allSteps) 237 | ) 238 | ) 239 | 240 | static member Capture1<'A, 'Tail>(next: PrintfFuncFactory<_, 'State, 'Residue, 'Result>) = 241 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun args initial -> 242 | (fun (arg1: 'A) -> 243 | let args = (box arg1 :: args) 244 | next.Invoke(args, initial) : 'Tail 245 | ) 246 | ) 247 | 248 | static member CaptureLittleA<'A, 'Tail>(next: PrintfFuncFactory<_, 'State, 'Residue, 'Result>) = 249 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun args initial -> 250 | (fun (f: 'State -> 'A -> 'Residue) (arg1: 'A) -> 251 | let args = box arg1 :: box (fun s (arg:objnull) -> f s (unbox arg)) :: args 252 | next.Invoke(args, initial) : 'Tail 253 | ) 254 | ) 255 | 256 | static member Capture2<'A, 'B, 'Tail>(next: PrintfFuncFactory<_, 'State, 'Residue, 'Result>) = 257 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun args initial -> 258 | (fun (arg1: 'A) (arg2: 'B) -> 259 | let args = box arg2 :: box arg1 :: args 260 | next.Invoke(args, initial) : 'Tail 261 | ) 262 | ) 263 | 264 | static member Capture3<'A, 'B, 'C, 'Tail>(next: PrintfFuncFactory<_, 'State, 'Residue, 'Result>) = 265 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun args initial -> 266 | (fun (arg1: 'A) (arg2: 'B) (arg3: 'C) -> 267 | let args = box arg3 :: box arg2 :: box arg1 :: args 268 | next.Invoke(args, initial) : 'Tail 269 | ) 270 | ) 271 | 272 | // Special case for format strings containing just one '%d' etc, i.e. StepWithArg then StepString. 273 | // This avoids allocating an argument array, and unfolds the single iteration of RunSteps. 274 | static member OneStepWithArg<'A>(prefix1, conv1, prefix2) = 275 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun _args initial -> 276 | // Note this is the actual computed/stored closure for 277 | // sprintf "prefix1 %d prefix2" 278 | // for any simple format specifiers, where conv1 and conv2 will depend on the format specifiers etc. 279 | (fun (arg1: 'A) -> 280 | let env = initial() 281 | env.WriteSkipEmpty prefix1 282 | env.Write(conv1 (box arg1)) 283 | env.WriteSkipEmpty prefix2 284 | env.Finish()) 285 | ) 286 | 287 | // Special case for format strings containing two simple formats like '%d %s' etc, i.e. 288 | ///StepWithArg then StepWithArg then StepString. This avoids allocating an argument array, 289 | // and unfolds the two iteration of RunSteps. 290 | static member TwoStepWithArg<'A, 'B>(prefix1, conv1, prefix2, conv2, prefix3) = 291 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun _args initial -> 292 | // Note this is the actual computed/stored closure for 293 | // sprintf "prefix1 %d prefix2 %s prefix3" 294 | // for any simple format specifiers, where conv1 and conv2 will depend on the format specifiers etc. 295 | (fun (arg1: 'A) (arg2: 'B) -> 296 | let env = initial() 297 | env.WriteSkipEmpty prefix1 298 | env.Write(conv1 (box arg1)) 299 | env.WriteSkipEmpty prefix2 300 | env.Write(conv2 (box arg2)) 301 | env.WriteSkipEmpty prefix3 302 | env.Finish()) 303 | ) 304 | 305 | let inline (===) a b = Object.ReferenceEquals(a, b) 306 | 307 | let inline boolToString v = if v then "true" else "false" 308 | 309 | let inline stringToSafeString v = 310 | match v with 311 | | null -> "" 312 | | _ -> v 313 | 314 | [] 315 | let DefaultPrecision = 6 316 | 317 | /// A wrapper struct used to slightly strengthen the types of "ValueConverter" objects produced during composition of 318 | /// the dynamic implementation. These are always functions but sometimes they take one argument, sometimes two. 319 | [] 320 | type ValueConverter internal (f: objnull) = 321 | member x.FuncObj = f 322 | 323 | static member inline Make (f: objnull -> string) = ValueConverter(box f) 324 | static member inline Make (f: objnull -> int -> string) = ValueConverter(box f) 325 | static member inline Make (f: objnull -> int-> int -> string) = ValueConverter(box f) 326 | 327 | let getFormatForFloat (ch: char) (prec: int) = ch.ToString() + prec.ToString() 328 | 329 | let normalizePrecision prec = min (max prec 0) 99 330 | 331 | /// Contains helpers to convert printer functions to functions that prints value with respect to specified justification 332 | /// There are two kinds to printers: 333 | /// 'T -> string - converts value to string - used for strings, basic integers etc.. 334 | /// string -> 'T -> string - converts value to string with given format string - used by numbers with floating point, typically precision is set via format string 335 | /// To support both categories there are two entry points: 336 | /// - withPadding - adapts first category 337 | /// - withPaddingFormatted - adapts second category 338 | module Padding = 339 | 340 | /// pad here is function that converts T to string with respect of justification 341 | /// basic - function that converts T to string without applying justification rules 342 | /// adaptPaddedFormatted returns boxed function that has various number of arguments depending on if width\precision flags has '*' value 343 | let adaptPaddedFormatted (spec: FormatSpecifier) getFormat (basic: string -> objnull -> string) (pad: string -> int -> objnull -> string) : ValueConverter = 344 | if spec.IsStarWidth then 345 | if spec.IsStarPrecision then 346 | // width=*, prec=* 347 | ValueConverter.Make (fun v width prec -> 348 | let fmt = getFormat (normalizePrecision prec) 349 | pad fmt width v) 350 | else 351 | // width=*, prec=? 352 | let prec = if spec.IsPrecisionSpecified then normalizePrecision spec.Precision else DefaultPrecision 353 | let fmt = getFormat prec 354 | ValueConverter.Make (fun v width -> 355 | pad fmt width v) 356 | 357 | elif spec.IsStarPrecision then 358 | if spec.IsWidthSpecified then 359 | // width=val, prec=* 360 | ValueConverter.Make (fun v prec -> 361 | let fmt = getFormat prec 362 | pad fmt spec.Width v) 363 | else 364 | // width=X, prec=* 365 | ValueConverter.Make (fun v prec -> 366 | let fmt = getFormat prec 367 | basic fmt v) 368 | else 369 | let prec = if spec.IsPrecisionSpecified then normalizePrecision spec.Precision else DefaultPrecision 370 | let fmt = getFormat prec 371 | if spec.IsWidthSpecified then 372 | // width=val, prec=* 373 | ValueConverter.Make ( 374 | pad fmt spec.Width) 375 | else 376 | // width=X, prec=* 377 | ValueConverter.Make ( 378 | basic fmt) 379 | 380 | /// pad here is function that converts T to string with respect of justification 381 | /// basic - function that converts T to string without applying justification rules 382 | /// adaptPadded returns boxed function that has various number of arguments depending on if width flags has '*' value 383 | let adaptPadded (spec: FormatSpecifier) (basic: objnull -> string) (pad: int -> objnull -> string) : ValueConverter = 384 | if spec.IsStarWidth then 385 | // width=*, prec=? 386 | ValueConverter.Make (fun v width -> 387 | pad width v) 388 | else 389 | if spec.IsWidthSpecified then 390 | // width=val, prec=* 391 | ValueConverter.Make ( 392 | pad spec.Width) 393 | else 394 | // width=X, prec=* 395 | ValueConverter.Make ( 396 | basic) 397 | 398 | let withPaddingFormatted (spec: FormatSpecifier) getFormat (defaultFormat: string) (f: string -> objnull -> string) left right : ValueConverter = 399 | if not (spec.IsWidthSpecified || spec.IsPrecisionSpecified) then 400 | ValueConverter.Make (f defaultFormat) 401 | else 402 | if isLeftJustify spec.Flags then 403 | adaptPaddedFormatted spec getFormat f left 404 | else 405 | adaptPaddedFormatted spec getFormat f right 406 | 407 | let withPadding (spec: FormatSpecifier) (f: objnull -> string) left right : ValueConverter = 408 | if not spec.IsWidthSpecified then 409 | ValueConverter.Make f 410 | else 411 | if isLeftJustify spec.Flags then 412 | adaptPadded spec f left 413 | else 414 | adaptPadded spec f right 415 | 416 | /// Contains functions to handle left/right justifications for non-numeric types (strings/bools) 417 | module Basic = 418 | let leftJustify (f: objnull -> string) padChar = 419 | fun (w: int) v -> 420 | (f v).PadRight(w, padChar) 421 | 422 | let rightJustify (f: objnull -> string) padChar = 423 | fun (w: int) v -> 424 | (f v).PadLeft(w, padChar) 425 | 426 | let withPadding (spec: FormatSpecifier) f = 427 | let padChar, _ = spec.GetPadAndPrefix false 428 | Padding.withPadding spec f (leftJustify f padChar) (rightJustify f padChar) 429 | 430 | /// Contains functions to handle left/right and no justification case for numbers 431 | module GenericNumber = 432 | 433 | let isPositive (n: obj) = 434 | match n with 435 | | :? int8 as n -> n >= 0y 436 | | :? uint8 -> true 437 | | :? int16 as n -> n >= 0s 438 | | :? uint16 -> true 439 | | :? int32 as n -> n >= 0 440 | | :? uint32 -> true 441 | | :? int64 as n -> n >= 0L 442 | | :? uint64 -> true 443 | | :? nativeint as n -> n >= 0n 444 | | :? unativeint -> true 445 | | :? single as n -> n >= 0.0f 446 | | :? double as n -> n >= 0.0 447 | | :? decimal as n -> n >= 0.0M 448 | | _ -> failwith "isPositive: unreachable" 449 | 450 | /// handles right justification when pad char = '0' 451 | /// this case can be tricky: 452 | /// - negative numbers, -7 should be printed as '-007', not '00-7' 453 | /// - positive numbers when prefix for positives is set: 7 should be '+007', not '00+7' 454 | let rightJustifyWithZeroAsPadChar (str: string) isNumber isPositive w (prefixForPositives: string) = 455 | System.Diagnostics.Debug.Assert(prefixForPositives.Length = 0 || prefixForPositives.Length = 1) 456 | if isNumber then 457 | if isPositive then 458 | prefixForPositives + (if w = 0 then str else str.PadLeft(w - prefixForPositives.Length, '0')) // save space to 459 | else 460 | if str.[0] = '-' then 461 | let str = str.Substring 1 462 | "-" + (if w = 0 then str else str.PadLeft(w - 1, '0')) 463 | else 464 | str.PadLeft(w, '0') 465 | else 466 | str.PadLeft(w, ' ') 467 | 468 | /// handler right justification when pad char = ' ' 469 | let rightJustifyWithSpaceAsPadChar (str: string) isNumber isPositive w (prefixForPositives: string) = 470 | System.Diagnostics.Debug.Assert(prefixForPositives.Length = 0 || prefixForPositives.Length = 1) 471 | (if isNumber && isPositive then prefixForPositives + str else str).PadLeft(w, ' ') 472 | 473 | /// handles left justification with formatting with 'G'\'g' - either for decimals or with 'g'\'G' is explicitly set 474 | let leftJustifyWithGFormat (str: string) isNumber isInteger isPositive w (prefixForPositives: string) padChar = 475 | if isNumber then 476 | let str = if isPositive then prefixForPositives + str else str 477 | // NOTE: difference - for 'g' format we use isInt check to detect situations when '5.0' is printed as '5' 478 | // in this case we need to override padding and always use ' ', otherwise we'll produce incorrect results 479 | if isInteger then 480 | str.PadRight(w, ' ') // don't pad integer numbers with '0' when 'g' format is specified (may yield incorrect results) 481 | else 482 | str.PadRight(w, padChar) // non-integer => string representation has point => can pad with any character 483 | else 484 | str.PadRight(w, ' ') // pad NaNs with ' ' 485 | 486 | let leftJustifyWithNonGFormat (str: string) isNumber isPositive w (prefixForPositives: string) padChar = 487 | if isNumber then 488 | let str = if isPositive then prefixForPositives + str else str 489 | str.PadRight(w, padChar) 490 | else 491 | str.PadRight(w, ' ') // pad NaNs with ' ' 492 | 493 | /// processes given string based depending on values isNumber\isPositive 494 | let noJustificationCore (str: string) isNumber isPositive prefixForPositives = 495 | if isNumber && isPositive then prefixForPositives + str 496 | else str 497 | 498 | /// noJustification handler for f: 'T -> string - basic integer types 499 | let noJustification (f: objnull -> string) (prefix: string) isUnsigned = 500 | if isUnsigned then 501 | fun (v: objnull) -> noJustificationCore (f v) true true prefix 502 | else 503 | fun (v: objnull) -> noJustificationCore (f v) true (isPositive v) prefix 504 | 505 | /// contains functions to handle left/right and no justification case for numbers 506 | module Integer = 507 | 508 | let eliminateNative (v: objnull) = 509 | match v with 510 | | :? nativeint as n -> 511 | if IntPtr.Size = 4 then box (n.ToInt32()) 512 | else box (n.ToInt64()) 513 | | :? unativeint as n -> 514 | if IntPtr.Size = 4 then box (uint32 (n.ToUInt32())) 515 | else box (uint64 (n.ToUInt64())) 516 | | _ -> v 517 | 518 | let rec toString (v: objnull) = 519 | match v with 520 | | :? int32 as n -> n.ToString(CultureInfo.InvariantCulture) 521 | | :? int64 as n -> n.ToString(CultureInfo.InvariantCulture) 522 | | :? sbyte as n -> n.ToString(CultureInfo.InvariantCulture) 523 | | :? byte as n -> n.ToString(CultureInfo.InvariantCulture) 524 | | :? int16 as n -> n.ToString(CultureInfo.InvariantCulture) 525 | | :? uint16 as n -> n.ToString(CultureInfo.InvariantCulture) 526 | | :? uint32 as n -> n.ToString(CultureInfo.InvariantCulture) 527 | | :? uint64 as n -> n.ToString(CultureInfo.InvariantCulture) 528 | | :? nativeint | :? unativeint -> toString (eliminateNative v) 529 | | _ -> failwith "toString: unreachable" 530 | 531 | let rec toFormattedString fmt (v: obj) = 532 | match v with 533 | | :? int32 as n -> n.ToString(fmt, CultureInfo.InvariantCulture) 534 | | :? int64 as n -> n.ToString(fmt, CultureInfo.InvariantCulture) 535 | | :? sbyte as n -> n.ToString(fmt, CultureInfo.InvariantCulture) 536 | | :? byte as n -> n.ToString(fmt, CultureInfo.InvariantCulture) 537 | | :? int16 as n -> n.ToString(fmt, CultureInfo.InvariantCulture) 538 | | :? uint16 as n -> n.ToString(fmt, CultureInfo.InvariantCulture) 539 | | :? uint32 as n -> n.ToString(fmt, CultureInfo.InvariantCulture) 540 | | :? uint64 as n -> n.ToString(fmt, CultureInfo.InvariantCulture) 541 | | :? nativeint | :? unativeint -> toFormattedString fmt (eliminateNative v) 542 | | _ -> failwith "toFormattedString: unreachable" 543 | 544 | let rec toUnsigned (v: objnull) = 545 | match v with 546 | | :? int32 as n -> box (uint32 n) 547 | | :? int64 as n -> box (uint64 n) 548 | | :? sbyte as n -> box (byte n) 549 | | :? int16 as n -> box (uint16 n) 550 | | :? nativeint | :? unativeint -> toUnsigned (eliminateNative v) 551 | | _ -> v 552 | 553 | /// Left justification handler for f: 'T -> string - basic integer types 554 | let leftJustify isGFormat (f: objnull -> string) (prefix: string) padChar isUnsigned = 555 | if isUnsigned then 556 | if isGFormat then 557 | fun (w: int) (v: objnull) -> 558 | GenericNumber.leftJustifyWithGFormat (f v) true true true w prefix padChar 559 | else 560 | fun (w: int) (v: objnull) -> 561 | GenericNumber.leftJustifyWithNonGFormat (f v) true true w prefix padChar 562 | else 563 | if isGFormat then 564 | fun (w: int) (v: objnull) -> 565 | GenericNumber.leftJustifyWithGFormat (f v) true true (GenericNumber.isPositive v) w prefix padChar 566 | else 567 | fun (w: int) (v: objnull) -> 568 | GenericNumber.leftJustifyWithNonGFormat (f v) true (GenericNumber.isPositive v) w prefix padChar 569 | 570 | /// Right justification handler for f: 'T -> string - basic integer types 571 | let rightJustify f (prefixForPositives: string) padChar isUnsigned = 572 | if isUnsigned then 573 | if padChar = '0' then 574 | fun (w: int) (v: objnull) -> 575 | GenericNumber.rightJustifyWithZeroAsPadChar (f v) true true w prefixForPositives 576 | else 577 | System.Diagnostics.Debug.Assert((padChar = ' ')) 578 | fun (w: int) (v: objnull) -> 579 | GenericNumber.rightJustifyWithSpaceAsPadChar (f v) true true w prefixForPositives 580 | else 581 | if padChar = '0' then 582 | fun (w: int) (v: objnull) -> 583 | GenericNumber.rightJustifyWithZeroAsPadChar (f v) true (GenericNumber.isPositive v) w prefixForPositives 584 | 585 | else 586 | System.Diagnostics.Debug.Assert((padChar = ' ')) 587 | fun (w: int) v -> 588 | GenericNumber.rightJustifyWithSpaceAsPadChar (f v) true (GenericNumber.isPositive v) w prefixForPositives 589 | 590 | /// Computes a new function from 'f' that wraps the basic conversion given 591 | /// by 'f' with padding for 0, spacing and justification, if the flags specify 592 | /// it. If they don't, f is made into a value converter 593 | let withPadding (spec: FormatSpecifier) isUnsigned (f: objnull -> string) = 594 | let allowZeroPadding = not (isLeftJustify spec.Flags) || spec.IsDecimalFormat 595 | let padChar, prefix = spec.GetPadAndPrefix allowZeroPadding 596 | Padding.withPadding spec 597 | (GenericNumber.noJustification f prefix isUnsigned) 598 | (leftJustify spec.IsGFormat f prefix padChar isUnsigned) 599 | (rightJustify f prefix padChar isUnsigned) 600 | 601 | let getValueConverter (spec: FormatSpecifier) : ValueConverter = 602 | match spec.TypeChar with 603 | | 'd' | 'i' -> 604 | withPadding spec false toString 605 | | 'u' -> 606 | withPadding spec true (toUnsigned >> toString) 607 | | 'x' -> 608 | withPadding spec true (toFormattedString "x") 609 | | 'X' -> 610 | withPadding spec true (toFormattedString "X") 611 | | 'o' -> 612 | withPadding spec true (fun (v: objnull) -> 613 | // Convert.ToInt64 throws for uint64 with values above int64 range so cast directly 614 | match toUnsigned v with 615 | | :? uint64 as u -> Convert.ToString(int64 u, 8) 616 | | u -> Convert.ToString(Convert.ToInt64 u, 8)) 617 | | 'B' -> 618 | withPadding spec true (fun (v: objnull) -> 619 | match toUnsigned v with 620 | | :? uint64 as u -> Convert.ToString(int64 u, 2) 621 | | u -> Convert.ToString(Convert.ToInt64 u, 2)) 622 | | _ -> invalidArg "spec" "Invalid integer format" 623 | 624 | module FloatAndDecimal = 625 | 626 | let rec toFormattedString fmt (v: obj) = 627 | match v with 628 | | :? single as n -> n.ToString(fmt, CultureInfo.InvariantCulture) 629 | | :? double as n -> n.ToString(fmt, CultureInfo.InvariantCulture) 630 | | :? decimal as n -> n.ToString(fmt, CultureInfo.InvariantCulture) 631 | | _ -> failwith "toFormattedString: unreachable" 632 | 633 | let isNumber (x: obj) = 634 | match x with 635 | | :? single as x -> 636 | not (Single.IsPositiveInfinity(x)) && 637 | not (Single.IsNegativeInfinity(x)) && 638 | not (Single.IsNaN(x)) 639 | | :? double as x -> 640 | not (Double.IsPositiveInfinity(x)) && 641 | not (Double.IsNegativeInfinity(x)) && 642 | not (Double.IsNaN(x)) 643 | | :? decimal -> true 644 | | _ -> failwith "isNumber: unreachable" 645 | 646 | let isInteger (n: obj) = 647 | match n with 648 | | :? single as n -> n % 1.0f = 0.0f 649 | | :? double as n -> n % 1. = 0. 650 | | :? decimal as n -> n % 1.0M = 0.0M 651 | | _ -> failwith "isInteger: unreachable" 652 | 653 | let noJustification (prefixForPositives: string) = 654 | fun (fmt: string) (v: obj) -> 655 | GenericNumber.noJustificationCore (toFormattedString fmt v) (isNumber v) (GenericNumber.isPositive v) prefixForPositives 656 | 657 | let leftJustify isGFormat (prefix: string) padChar = 658 | if isGFormat then 659 | fun (fmt: string) (w: int) (v: obj) -> 660 | GenericNumber.leftJustifyWithGFormat (toFormattedString fmt v) (isNumber v) (isInteger v) (GenericNumber.isPositive v) w prefix padChar 661 | else 662 | fun (fmt: string) (w: int) (v: obj) -> 663 | GenericNumber.leftJustifyWithNonGFormat (toFormattedString fmt v) (isNumber v) (GenericNumber.isPositive v) w prefix padChar 664 | 665 | let rightJustify (prefixForPositives: string) padChar = 666 | if padChar = '0' then 667 | fun (fmt: string) (w: int) (v: obj) -> 668 | GenericNumber.rightJustifyWithZeroAsPadChar (toFormattedString fmt v) (isNumber v) (GenericNumber.isPositive v) w prefixForPositives 669 | else 670 | System.Diagnostics.Debug.Assert((padChar = ' ')) 671 | fun (fmt: string) (w: int) (v: obj) -> 672 | GenericNumber.rightJustifyWithSpaceAsPadChar (toFormattedString fmt v) (isNumber v) (GenericNumber.isPositive v) w prefixForPositives 673 | 674 | let withPadding (spec: FormatSpecifier) getFormat defaultFormat = 675 | let padChar, prefix = spec.GetPadAndPrefix true 676 | Padding.withPaddingFormatted spec getFormat defaultFormat 677 | (noJustification prefix) 678 | (leftJustify spec.IsGFormat prefix padChar) 679 | (rightJustify prefix padChar) 680 | 681 | type ObjectPrinter = 682 | 683 | static member ObjectToString(spec: FormatSpecifier) : ValueConverter = 684 | Basic.withPadding spec (fun (v: objnull) -> 685 | match v with 686 | | null -> "" 687 | | x -> x.ToString()) 688 | 689 | /// Convert an interpoland to a string 690 | static member InterpolandToString(spec: FormatSpecifier) : ValueConverter = 691 | let fmt = 692 | match spec.InteropHoleDotNetFormat with 693 | | ValueNone -> null 694 | | ValueSome fmt -> "{0:" + fmt + "}" 695 | Basic.withPadding spec (fun (vobj: objnull) -> 696 | match vobj with 697 | | null -> "" 698 | | x -> 699 | match fmt with 700 | | null -> x.ToString() 701 | | fmt -> String.Format(fmt, x)) 702 | 703 | static member GenericToStringCore(v: 'T, opts: FormatOptions, bindingFlags) = 704 | let vty = 705 | match box v with 706 | | null -> typeof<'T> 707 | | _ -> v.GetType() 708 | Display.anyToStringForPrintf opts bindingFlags (v, vty) 709 | 710 | static member GenericToString<'T>(spec: FormatSpecifier) : ValueConverter = 711 | let bindingFlags = 712 | if isPlusForPositives spec.Flags then BindingFlags.Public ||| BindingFlags.NonPublic 713 | else BindingFlags.Public 714 | 715 | let useZeroWidth = isPadWithZeros spec.Flags 716 | let opts = 717 | let o = FormatOptions.Default 718 | let o = 719 | if useZeroWidth then { o with PrintWidth = 0} 720 | elif spec.IsWidthSpecified then { o with PrintWidth = spec.Width} 721 | else o 722 | if spec.IsPrecisionSpecified then { o with PrintSize = spec.Precision} 723 | else o 724 | 725 | match spec.IsStarWidth, spec.IsStarPrecision with 726 | | true, true -> 727 | ValueConverter.Make (fun (vobj: objnull) (width: int) (prec: int) -> 728 | let v = unbox<'T> vobj 729 | let opts = { opts with PrintSize = prec } 730 | let opts = if not useZeroWidth then { opts with PrintWidth = width} else opts 731 | ObjectPrinter.GenericToStringCore(v, opts, bindingFlags) 732 | ) 733 | 734 | | true, false -> 735 | ValueConverter.Make (fun (vobj: objnull) (width: int) -> 736 | let v = unbox<'T> vobj 737 | let opts = if not useZeroWidth then { opts with PrintWidth = width} else opts 738 | ObjectPrinter.GenericToStringCore(v, opts, bindingFlags)) 739 | 740 | | false, true -> 741 | ValueConverter.Make (fun (vobj: objnull) (prec: int) -> 742 | let v = unbox<'T> vobj 743 | let opts = { opts with PrintSize = prec } 744 | ObjectPrinter.GenericToStringCore(v, opts, bindingFlags) ) 745 | 746 | | false, false -> 747 | ValueConverter.Make (fun (vobj: objnull) -> 748 | let v = unbox<'T> vobj 749 | ObjectPrinter.GenericToStringCore(v, opts, bindingFlags)) 750 | 751 | let basicFloatToString spec = 752 | let defaultFormat = getFormatForFloat spec.TypeChar DefaultPrecision 753 | FloatAndDecimal.withPadding spec (getFormatForFloat spec.TypeChar) defaultFormat 754 | 755 | let private AllStatics = BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.Static 756 | 757 | let mi_GenericToString = typeof.GetMethod("GenericToString", AllStatics) 758 | 759 | /// **MasterOfFoo specifics** 760 | /// 761 | /// The code of this function is the code of `getValueConverter` renamed to `getValueConverterCore` as we need a 762 | /// different return type for converters. 763 | let private getValueConverterCore (ty: Type) (spec: FormatSpecifier) : ValueConverter = 764 | match spec.TypeChar with 765 | | 'b' -> 766 | Basic.withPadding spec (unbox >> boolToString) 767 | | 's' -> 768 | Basic.withPadding spec (unbox >> stringToSafeString) 769 | | 'c' -> 770 | Basic.withPadding spec (fun (c: objnull) -> (unbox c).ToString()) 771 | | 'M' -> 772 | FloatAndDecimal.withPadding spec (fun _ -> "G") "G" // %M ignores precision 773 | | 'd' | 'i' | 'u' | 'B' | 'o' | 'x' | 'X' -> 774 | Integer.getValueConverter spec 775 | | 'e' | 'E' 776 | | 'f' | 'F' 777 | | 'g' | 'G' -> 778 | basicFloatToString spec 779 | | 'A' -> 780 | let mi = mi_GenericToString.MakeGenericMethod ty 781 | mi.Invoke(null, [| box spec |]) |> unbox 782 | | 'O' -> 783 | ObjectPrinter.ObjectToString(spec) 784 | | 'P' -> 785 | ObjectPrinter.InterpolandToString(spec) 786 | | _ -> 787 | raise (ArgumentException(sprintf "Bad format specifier: %c" spec.TypeChar)) 788 | 789 | /// **MasterOfFoo specifics** 790 | /// 791 | /// This type is equivalent to the `ValueConverter` type from the F# source code but with `PrintableElement` as 792 | /// the final result instead of `string`. 793 | [] 794 | type PrintableValueConverter private (f: obj) = 795 | member x.FuncObj = f 796 | 797 | static member inline Make<'t> (f: obj -> PrintableElement) = PrintableValueConverter(box f) 798 | static member inline Make<'t> (f: obj -> int -> PrintableElement) = PrintableValueConverter(box f) 799 | static member inline Make<'t> (f: obj -> int-> int -> PrintableElement) = PrintableValueConverter(box f) 800 | 801 | /// **MasterOfFoo specifics** 802 | /// 803 | /// As our converters need to return a `PrintableElement` instead of a `string`, we need to adapt the 804 | /// `getValueConverter` by guessing how many arguments the function will take (Depending on if star width and/or 805 | /// star precision are used), generate the correct function and box it. 806 | let getValueConverter (ty : Type) (spec : FormatSpecifier) : PrintableValueConverter = 807 | let et = PrintableElementType.FromFormatSpecifier 808 | let realUntyped = getValueConverterCore ty spec 809 | if spec.IsStarWidth && spec.IsStarPrecision then 810 | let real = realUntyped.FuncObj :?> (obj -> int -> int -> string) 811 | PrintableValueConverter.Make(fun (x: obj) width prec -> 812 | let printer = fun () -> real x width prec 813 | PrintableElement(printer, x, et, ty, spec, width, prec)) 814 | else if spec.IsStarWidth || spec.IsStarPrecision then 815 | let real = realUntyped.FuncObj :?> (obj -> int -> string) 816 | if spec.IsStarWidth then 817 | PrintableValueConverter.Make(fun (x: obj) width -> 818 | let printer = fun () -> real x width 819 | PrintableElement(printer, x, et, ty, spec, width, NotSpecifiedValue)) 820 | else 821 | PrintableValueConverter.Make(fun (x: obj) prec -> 822 | let printer = fun () -> real x prec 823 | PrintableElement(printer, x, et, ty, spec, NotSpecifiedValue, prec)) 824 | else 825 | let real = realUntyped.FuncObj :?> (obj -> string) 826 | PrintableValueConverter.Make(fun (x: obj) -> 827 | let printer = fun () -> real x 828 | PrintableElement(printer, x, et, ty, spec, NotSpecifiedValue, NotSpecifiedValue)) 829 | 830 | let extractCurriedArguments (ty: Type) n = 831 | System.Diagnostics.Debug.Assert(n = 1 || n = 2 || n = 3, "n = 1 || n = 2 || n = 3") 832 | let buf = Array.zeroCreate n 833 | let rec go (ty: Type) i = 834 | if i < n then 835 | match ty.GetGenericArguments() with 836 | | [| argTy; retTy|] -> 837 | buf.[i] <- argTy 838 | go retTy (i + 1) 839 | | _ -> failwith (String.Format("Expected function with {0} arguments", n)) 840 | else 841 | System.Diagnostics.Debug.Assert((i = n), "i = n") 842 | (buf, ty) 843 | go ty 0 844 | 845 | let MAX_CAPTURE = 3 846 | 847 | /// Parses format string and creates resulting step list and printer factory function. 848 | [] 849 | type FormatParser<'Printer, 'State, 'Residue, 'Result>(fmt: string) = 850 | 851 | let buildCaptureFunc (spec: FormatSpecifier, allSteps, argTys: Type array, retTy, nextInfo) = 852 | let (next:obj, nextCanCombine: bool, nextArgTys: Type array, nextRetTy, nextNextOpt) = nextInfo 853 | assert (argTys.Length > 0) 854 | 855 | // See if we can compress a capture to a multi-capture 856 | // CaptureN + Final --> CaptureFinalN 857 | // Capture1 + Capture1 --> Capture2 858 | // Capture1 + Capture2 --> Capture3 859 | // Capture2 + Capture1 --> Capture3 860 | match argTys.Length, nextArgTys.Length with 861 | | _ when spec.TypeChar = 'a' -> 862 | // %a has an existential type which must be converted to obj 863 | assert (argTys.Length = 2) 864 | let captureMethName = "CaptureLittleA" 865 | let mi = typeof>.GetMethod(captureMethName, AllStatics) 866 | let mi = mi.MakeGenericMethod([| argTys.[1]; retTy |]) 867 | let factoryObj = mi.Invoke(null, [| next |]) 868 | factoryObj, false, argTys, retTy, None 869 | 870 | | n1, n2 when nextCanCombine && n1 + n2 <= MAX_CAPTURE -> 871 | // 'next' is thrown away on this path and replaced by a combined Capture 872 | let captureCount = n1 + n2 873 | let combinedArgTys = Array.append argTys nextArgTys 874 | match nextNextOpt with 875 | | None -> 876 | let captureMethName = "CaptureFinal" + string captureCount 877 | let mi = typeof>.GetMethod(captureMethName, AllStatics) 878 | let mi = mi.MakeGenericMethod(combinedArgTys) 879 | let factoryObj = mi.Invoke(null, [| allSteps |]) 880 | factoryObj, true, combinedArgTys, nextRetTy, None 881 | | Some nextNext -> 882 | let captureMethName = "Capture" + string captureCount 883 | let mi = typeof>.GetMethod(captureMethName, AllStatics) 884 | let mi = mi.MakeGenericMethod(Array.append combinedArgTys [| nextRetTy |]) 885 | let factoryObj = mi.Invoke(null, [| nextNext |]) 886 | factoryObj, true, combinedArgTys, nextRetTy, nextNextOpt 887 | 888 | | captureCount, _ -> 889 | let captureMethName = "Capture" + string captureCount 890 | let mi = typeof>.GetMethod(captureMethName, AllStatics) 891 | let mi = mi.MakeGenericMethod(Array.append argTys [| retTy |]) 892 | let factoryObj = mi.Invoke(null, [| next |]) 893 | factoryObj, true, argTys, retTy, Some next 894 | 895 | let buildStep (spec: FormatSpecifier) (argTys: Type array) prefix = 896 | if spec.TypeChar = 'a' then 897 | StepLittleA prefix 898 | elif spec.TypeChar = 't' then 899 | StepLittleT prefix 900 | elif spec.IsStarPrecision || spec.IsStarWidth then 901 | let isTwoStar = (spec.IsStarWidth = spec.IsStarPrecision) 902 | match isTwoStar, spec.TypeChar with 903 | | false, '%' -> StepPercentStar1 prefix 904 | | true, '%' -> StepPercentStar2 prefix 905 | | _ -> 906 | // For curried interpolated string format processing, the static types of the '%A' arguments 907 | // are provided via the argument typed extracted from the curried function. They are known on first phase. 908 | let argTy = match argTys with null -> typeof | _ -> argTys.[argTys.Length - 1] 909 | let conv = getValueConverter argTy spec 910 | if isTwoStar then 911 | let convFunc = conv.FuncObj :?> (objnull -> int -> int -> PrintableElement) 912 | StepStar2 (prefix, convFunc) 913 | else 914 | let convFunc = conv.FuncObj :?> (objnull -> int -> PrintableElement) 915 | StepStar1 (prefix, convFunc) 916 | else 917 | // For interpolated string format processing, the static types of the '%A' arguments 918 | // are provided via CaptureTypes and are only known on second phase. 919 | match argTys with 920 | | null when spec.TypeChar = 'A' -> 921 | let convFunc arg argTy = 922 | let mi = mi_GenericToString.MakeGenericMethod [| argTy |] 923 | let f = mi.Invoke(null, [| box spec |]) :?> ValueConverter 924 | let f2 = f.FuncObj :?> (objnull -> string) 925 | let printer = fun () -> f2 arg 926 | PrintableElement( 927 | printer, 928 | arg, 929 | PrintableElementType.FromFormatSpecifier, 930 | argTy, 931 | spec, 932 | NotSpecifiedValue, NotSpecifiedValue) 933 | 934 | StepWithTypedArg (prefix, convFunc) 935 | 936 | | _ -> 937 | // For curried interpolated string format processing, the static types of the '%A' arguments 938 | // are provided via the argument typed extracted from the curried function. They are known on first phase. 939 | let argTy = match argTys with null -> typeof | _ -> argTys.[0] 940 | let conv = getValueConverter argTy spec 941 | let convFunc = conv.FuncObj :?> (objnull -> PrintableElement) 942 | StepWithArg (prefix, convFunc) 943 | 944 | let parseSpec (i: byref) = 945 | i <- i + 1 946 | let flags = FormatString.parseFlags fmt &i 947 | let width = FormatString.parseWidth fmt &i 948 | let precision = FormatString.parsePrecision fmt &i 949 | let typeChar = FormatString.parseTypeChar fmt &i 950 | let interpHoleDotnetFormat = FormatString.parseInterpolatedHoleDotNetFormat typeChar fmt &i 951 | 952 | // Skip %P insertion points added after %d{...} etc. in interpolated strings 953 | FormatString.skipInterpolationHole typeChar fmt &i 954 | 955 | let spec = 956 | { TypeChar = typeChar 957 | Precision = precision 958 | Flags = flags 959 | Width = width 960 | InteropHoleDotNetFormat = interpHoleDotnetFormat } 961 | spec 962 | 963 | // The steps, populated on-demand. This is for the case where the string is being used 964 | // with interpolands captured in the Format object, including the %A capture types. 965 | // 966 | // We may initialize this twice, but the assignment is atomic and the computation will give functionally 967 | // identical results each time, so it is ok. 968 | let mutable stepsForCapturedFormat = Unchecked.defaultof<_> 969 | 970 | // The function factory, populated on-demand, for the case where the string is being used to make a curried function for printf. 971 | // 972 | // We may initialize this twice, but the assignment is atomic and the computation will give functionally 973 | // identical results each time, so it is ok. 974 | let mutable factory = Unchecked.defaultof> 975 | let mutable printer = Unchecked.defaultof<'Printer> 976 | 977 | // The function factory, populated on-demand. 978 | // 979 | // We may initialize this twice, but the assignment is atomic and the computation will give functionally 980 | // identical results each time, so it is ok. 981 | let mutable stringCount = 0 982 | 983 | // A simplified parser. For the case where the string is being used with interpolands captured in the Format object. 984 | let rec parseAndCreateStepsForCapturedFormatAux steps (prefix: PrintableElement) (i: byref) = 985 | if i >= fmt.Length then 986 | let step = StepString(prefix) 987 | let allSteps = revToArray 1 steps 988 | allSteps.[allSteps.Length-1] <- step 989 | stringCount <- Step.BlockCount allSteps 990 | stepsForCapturedFormat <- allSteps 991 | else 992 | let spec = parseSpec &i 993 | let suffix = FormatString.findNextFormatSpecifier fmt &i 994 | let step = buildStep spec null prefix 995 | parseAndCreateStepsForCapturedFormatAux (step::steps) suffix &i 996 | 997 | let parseAndCreateStepsForCapturedFormat () = 998 | let mutable i = 0 999 | let prefix = FormatString.findNextFormatSpecifier fmt &i 1000 | parseAndCreateStepsForCapturedFormatAux [] prefix &i 1001 | 1002 | /// The more advanced parser which both builds the steps (with %A types extracted from the funcTy), 1003 | /// and produces a curried function value of the right type guided by funcTy 1004 | let rec parseAndCreateFuncFactoryAux steps (prefix: PrintableElement) (funcTy: Type) (i: byref) = 1005 | 1006 | if i >= fmt.Length then 1007 | let step = StepString(prefix) 1008 | let allSteps = revToArray 1 steps 1009 | allSteps.[allSteps.Length-1] <- step 1010 | let last = Specializations<'State, 'Residue, 'Result>.Final0(allSteps) 1011 | stringCount <- Step.BlockCount allSteps 1012 | let nextInfo = (box last, true, [| |], funcTy, None) 1013 | (allSteps, nextInfo) 1014 | else 1015 | assert (fmt.[i] = '%') 1016 | let spec = parseSpec &i 1017 | let suffix = FormatString.findNextFormatSpecifier fmt &i 1018 | let n = spec.ArgCount 1019 | let (argTys, retTy) = extractCurriedArguments funcTy n 1020 | let step = buildStep spec argTys prefix 1021 | let (allSteps, nextInfo) = parseAndCreateFuncFactoryAux (step::steps) suffix retTy &i 1022 | let nextInfoNew = buildCaptureFunc (spec, allSteps, argTys, retTy, nextInfo) 1023 | (allSteps, nextInfoNew) 1024 | 1025 | let parseAndCreateFunctionFactory () = 1026 | let funcTy = typeof<'Printer> 1027 | 1028 | // Find the first format specifier 1029 | let mutable i = 0 1030 | let prefix = FormatString.findNextFormatSpecifier fmt &i 1031 | 1032 | let (allSteps, (factoryObj, _, combinedArgTys, _, _)) = parseAndCreateFuncFactoryAux [] prefix funcTy &i 1033 | 1034 | // If there are no format specifiers then take a simple path 1035 | match allSteps with 1036 | | [| StepString prefix |] -> 1037 | PrintfFuncFactory<_, 'State, 'Residue, 'Result>(fun _args initial -> 1038 | let env = initial() 1039 | env.WriteSkipEmpty prefix 1040 | env.Finish() 1041 | ) |> box 1042 | 1043 | // If there is one simple format specifier then we can create an even better factory function 1044 | | [| StepWithArg (prefix1, conv1); StepString prefix2 |] -> 1045 | let captureMethName = "OneStepWithArg" 1046 | let mi = typeof>.GetMethod(captureMethName, AllStatics) 1047 | let mi = mi.MakeGenericMethod(combinedArgTys) 1048 | let factoryObj = mi.Invoke(null, [| box prefix1; box conv1; box prefix2 |]) 1049 | factoryObj 1050 | 1051 | // If there are two simple format specifiers then we can create an even better factory function 1052 | | [| StepWithArg (prefix1, conv1); StepWithArg (prefix2, conv2); StepString prefix3 |] -> 1053 | let captureMethName = "TwoStepWithArg" 1054 | let mi = typeof>.GetMethod(captureMethName, AllStatics) 1055 | let mi = mi.MakeGenericMethod(combinedArgTys) 1056 | let factoryObj = mi.Invoke(null, [| box prefix1; box conv1; box prefix2; box conv2; box prefix3 |]) 1057 | factoryObj 1058 | 1059 | | _ -> 1060 | factoryObj 1061 | 1062 | /// The format string, used to help identify the cache entry (the cache index types are taken 1063 | /// into account as well). 1064 | member _.FormatString = fmt 1065 | 1066 | /// The steps involved in executing the format string when interpolands are captured 1067 | /// 1068 | /// If %A patterns are involved these steps are only accurate when the %A capture types 1069 | /// are given in the format string through interpolation capture. 1070 | member _.GetStepsForCapturedFormat() = 1071 | match stepsForCapturedFormat with 1072 | | null -> parseAndCreateStepsForCapturedFormat () 1073 | | _ -> () 1074 | stepsForCapturedFormat 1075 | 1076 | /// The number of strings produced for a sprintf 1077 | member _.BlockCount = stringCount 1078 | 1079 | /// The factory function used to generate the result or the resulting function. 1080 | member _.GetCurriedPrinterFactory() = 1081 | match box factory with 1082 | | null -> 1083 | let factoryObj = parseAndCreateFunctionFactory () 1084 | let p = (factoryObj :?> PrintfFuncFactory<'Printer, 'State, 'Residue, 'Result>) 1085 | // We may initialize this twice, but the assignment is atomic and the computation will give functionally 1086 | // identical results each time it is ok 1087 | factory <- p 1088 | p 1089 | | _ -> factory 1090 | 1091 | /// 2-level cache, keyed by format string and index types 1092 | type Cache<'Printer, 'State, 'Residue, 'Result>() = 1093 | 1094 | /// 1st level cache (type-indexed). Stores last value that was consumed by the current thread in 1095 | /// thread-static field thus providing shortcuts for scenarios when printf is called in tight loop. 1096 | [] 1097 | static val mutable private mostRecent: FormatParser<'Printer, 'State, 'Residue, 'Result> 1098 | 1099 | // 2nd level cache (type-indexed). Dictionary that maps format string to the corresponding cache entry 1100 | static let mutable dict : ConcurrentDictionary> = null 1101 | 1102 | static member GetParser(format: Format<'Printer, 'State, 'Residue, 'Result>) = 1103 | let recent = Cache<'Printer, 'State, 'Residue, 'Result>.mostRecent 1104 | let fmt = format.Value 1105 | if isNull recent then 1106 | let parser = FormatParser(fmt) 1107 | Cache<'Printer, 'State, 'Residue, 'Result>.mostRecent <- parser 1108 | parser 1109 | elif fmt.Equals recent.FormatString then 1110 | recent 1111 | else 1112 | // Initialize the 2nd level cache if necessary. Note there's a race condition but it doesn't 1113 | // matter if we initialize these values twice (and lose one entry) 1114 | if isNull dict then 1115 | dict <- ConcurrentDictionary<_,_>() 1116 | 1117 | let parser = 1118 | match dict.TryGetValue(fmt) with 1119 | | true, res -> res 1120 | | _ -> 1121 | let parser = FormatParser(fmt) 1122 | // There's a race condition - but the computation is functional and it doesn't matter if we do it twice 1123 | dict.TryAdd(fmt, parser) |> ignore 1124 | parser 1125 | Cache<'Printer, 'State, 'Residue, 'Result>.mostRecent <- parser 1126 | parser 1127 | --------------------------------------------------------------------------------