diff --git a/.github/actions/spell-check/dictionary/apis.txt b/.github/actions/spell-check/dictionary/apis.txt index 17b73e780d6..fdb6a2dcd05 100644 --- a/.github/actions/spell-check/dictionary/apis.txt +++ b/.github/actions/spell-check/dictionary/apis.txt @@ -8,11 +8,12 @@ EXPCMDSTATE fullkbd href IAsync -IBox IBind +IBox IClass IComparable ICustom +IDialog IDirect IExplorer IMap @@ -27,6 +28,7 @@ NOAGGREGATION NOREDIRECTIONBITMAP oaidl ocidl +PAGESCROLL RETURNCMD rfind roundf diff --git a/.github/actions/spell-check/dictionary/dictionary.txt b/.github/actions/spell-check/dictionary/dictionary.txt index d3590ea587f..c01a75b1927 100644 --- a/.github/actions/spell-check/dictionary/dictionary.txt +++ b/.github/actions/spell-check/dictionary/dictionary.txt @@ -284908,6 +284908,7 @@ overliing overliking overlimit overline +overlined overling overlinger overlinked diff --git a/.github/actions/spell-check/dictionary/microsoft.txt b/.github/actions/spell-check/dictionary/microsoft.txt index 960efdbd7d5..5e5d2ee3164 100644 --- a/.github/actions/spell-check/dictionary/microsoft.txt +++ b/.github/actions/spell-check/dictionary/microsoft.txt @@ -1,4 +1,5 @@ ACLs +backplating DACL DACLs LKG diff --git a/.github/actions/spell-check/expect/alphabet.txt b/.github/actions/spell-check/expect/alphabet.txt index 3644d8e5922..6d395ae7a5d 100644 --- a/.github/actions/spell-check/expect/alphabet.txt +++ b/.github/actions/spell-check/expect/alphabet.txt @@ -8,6 +8,7 @@ abcdefghijklmnop ABCDEFGHIJKLMNOPQRST abcdefghijklmnopqrstuvwxyz ABE +BBGGRR BBBBBBBBBBBBBBDDDD QQQQQQQQQQABCDEFGHIJ QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQ diff --git a/.github/actions/spell-check/expect/expect.txt b/.github/actions/spell-check/expect/expect.txt index 7fe9b08c3e8..c4ab573558d 100644 --- a/.github/actions/spell-check/expect/expect.txt +++ b/.github/actions/spell-check/expect/expect.txt @@ -202,6 +202,7 @@ capslock CARETBLINKINGENABLED CARRIAGERETURN cascadia +cassert catid carlos zamora @@ -270,12 +271,14 @@ clipbrd CLIPCHILDREN CLIPSIBLINGS cliutils +clocale closetest cloudconsole cls CLSCTX clsid CLUSTERMAP +cmath cmatrix cmder CMDEXT @@ -419,12 +422,16 @@ Csr csrmsg CSRSS csrutil +cstdarg +cstddef cstdio +cstdlib cstr cstring cstyle CSwitch CText +ctime ctl ctlseqs Ctlv @@ -590,6 +597,7 @@ devops Dext df DFactory +DFMT dh dialogbox diffing @@ -1143,6 +1151,7 @@ IUI IUia IUnknown ivalid +IValue IVector IWait iwch @@ -1255,6 +1264,7 @@ Loremipsumdolorsitamet lowercased loword lparam +lparen LPBYTE LPCCH lpch @@ -1614,6 +1624,7 @@ OSCCT OSCFG OSCRCC OSCSCC +OSCSCB OSCWT OSDEPENDSROOT osfhandle @@ -1979,6 +1990,7 @@ roadmap robomac roundtrip ROWSTOSCROLL +rparen RRF RRRGGGBB rtcore @@ -2125,6 +2137,7 @@ SIGDN SINGLEFLAG SINGLETHREADED siup +SIZEBOX sizeof SIZESCROLL SKIPFONT diff --git a/.github/actions/spell-check/patterns/patterns.txt b/.github/actions/spell-check/patterns/patterns.txt index 3833627a51d..413709e1202 100644 --- a/.github/actions/spell-check/patterns/patterns.txt +++ b/.github/actions/spell-check/patterns/patterns.txt @@ -14,5 +14,8 @@ Scro\&ll # selectionInput.cpp :\\windows\\syste\b TestUtils::VerifyExpectedString\(tb, L"[^"]+" -hostSm\.ProcessString\(L"[^"]+" +(?:hostSm|mach)\.ProcessString\(L"[^"]+" \b([A-Za-z])\1{3,}\b +Base64::s_(?:En|De)code\(L"[^"]+" +VERIFY_ARE_EQUAL\(L"[^"]+" +L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\+/" diff --git a/OpenConsole.sln b/OpenConsole.sln index b4d94c467fd..4d0d2e61481 100644 --- a/OpenConsole.sln +++ b/OpenConsole.sln @@ -314,6 +314,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfTerminalTestNetCore", "s {84848BFA-931D-42CE-9ADF-01EE54DE7890} = {84848BFA-931D-42CE-9ADF-01EE54DE7890} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wt", "src\cascadia\wt\wt.vcxproj", "{506FD703-BAA7-4F6E-9361-64F550EC8FCA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution AuditMode|Any CPU = AuditMode|Any CPU @@ -1995,6 +1997,33 @@ Global {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Release|DotNet_x86Test.Build.0 = Release|x86 {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Release|x64.ActiveCfg = Release|Any CPU {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Release|x86.ActiveCfg = Release|Any CPU + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|DotNet_x64Test.ActiveCfg = AuditMode|Win32 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|DotNet_x86Test.ActiveCfg = AuditMode|Win32 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|x64.ActiveCfg = AuditMode|x64 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|x64.Build.0 = AuditMode|x64 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|x86.ActiveCfg = AuditMode|Win32 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|x86.Build.0 = AuditMode|Win32 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|ARM64.Build.0 = Debug|ARM64 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|x64.ActiveCfg = Debug|x64 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|x64.Build.0 = Debug|x64 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|x86.ActiveCfg = Debug|Win32 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|x86.Build.0 = Debug|Win32 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|Any CPU.ActiveCfg = Release|Win32 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|ARM64.ActiveCfg = Release|ARM64 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|ARM64.Build.0 = Release|ARM64 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|DotNet_x64Test.ActiveCfg = Release|Win32 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|DotNet_x86Test.ActiveCfg = Release|Win32 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x64.ActiveCfg = Release|x64 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x64.Build.0 = Release|x64 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x86.ActiveCfg = Release|Win32 + {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2075,6 +2104,7 @@ Global {067F0A06-FCB7-472C-96E9-B03B54E8E18D} = {59840756-302F-44DF-AA47-441A9D673202} {6BAE5851-50D5-4934-8D5E-30361A8A40F3} = {81C352DB-1818-45B7-A284-18E259F1CC87} {1588FD7C-241E-4E7D-9113-43735F3E6BAD} = {59840756-302F-44DF-AA47-441A9D673202} + {506FD703-BAA7-4F6E-9361-64F550EC8FCA} = {59840756-302F-44DF-AA47-441A9D673202} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271} diff --git a/build/scripts/Test-WindowsTerminalPackage.ps1 b/build/scripts/Test-WindowsTerminalPackage.ps1 index 5958a69f8b5..88cb14bbf97 100644 --- a/build/scripts/Test-WindowsTerminalPackage.ps1 +++ b/build/scripts/Test-WindowsTerminalPackage.ps1 @@ -101,6 +101,11 @@ Try { Throw "Failed to find cpprest142_2_10.dll -- check the WAP packaging project" } + If (($null -eq (Get-Item "$AppxPackageRootPath\wtd.exe" -EA:Ignore)) -And + ($null -eq (Get-Item "$AppxPackageRootPath\wt.exe" -EA:Ignore))) { + Throw "Failed to find wt.exe/wtd.exe -- check the WAP packaging project" + } + } Finally { Remove-Item -Recurse -Force $AppxPackageRootPath } diff --git a/custom.props b/custom.props index a44cb494594..272e30a61fa 100644 --- a/custom.props +++ b/custom.props @@ -5,7 +5,7 @@ true 2020 1 - 1 + 2 Windows Terminal diff --git a/dep/gsl b/dep/gsl index 7e99e76c976..0f6dbc9e291 160000 --- a/dep/gsl +++ b/dep/gsl @@ -1 +1 @@ -Subproject commit 7e99e76c9761d0d0b0848b91f8648830670ee872 +Subproject commit 0f6dbc9e2915ef5c16830f3fa3565738de2a9230 diff --git a/doc/building.md b/doc/building.md index b99c139c333..27c1e7d806e 100644 --- a/doc/building.md +++ b/doc/building.md @@ -67,12 +67,12 @@ To update the version of a given package, use the following snippet where: - `$PackageName` is the name of the package, e.g. Microsoft.UI.Xaml -- `$OldVersionNumber` is the version number currently used, e.g. 2.4.2-prerelease.200604001 +- `$OldVersionNumber` is the version number currently used, e.g. 2.5.0-prerelease.200609001 - `$NewVersionNumber` is the version number you want to migrate to, e.g. 2.4.200117003-prerelease Example usage: -`git grep -z -l Microsoft.UI.Xaml | xargs -0 sed -i -e 's/2.4.2-prerelease.200604001/2.4.200117003-prerelease/g'` +`git grep -z -l Microsoft.UI.Xaml | xargs -0 sed -i -e 's/2.5.0-prerelease.200609001/2.4.200117003-prerelease/g'` ## Using .nupkg files instead of downloaded Nuget packages If you want to use .nupkg files instead of the downloaded Nuget package, you can do this with the following steps: diff --git a/doc/cascadia/SettingsSchema.md b/doc/cascadia/SettingsSchema.md index ffca902b73f..9c58edc89f4 100644 --- a/doc/cascadia/SettingsSchema.md +++ b/doc/cascadia/SettingsSchema.md @@ -9,12 +9,13 @@ Properties listed below affect the entire window, regardless of the profile sett | `alwaysShowTabs` | _Required_ | Boolean | `true` | When set to `true`, tabs are always displayed. When set to `false` and `showTabsInTitlebar` is set to `false`, tabs only appear after typing Ctrl + T. | | `copyOnSelect` | Optional | Boolean | `false` | When set to `true`, a selection is immediately copied to your clipboard upon creation. When set to `false`, the selection persists and awaits further action. | | `copyFormatting` | Optional | Boolean | `false` | When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. | +| `largePasteWarning` | Optional | Boolean | `true` | When set to `true`, trying to paste text with more than 5 KiB of characters will display a warning asking you whether to continue or not with the paste. | +| `multiLinePasteWarning` | Optional | Boolean | `true` | When set to `true`, trying to paste text with a _new line_ character will display a warning asking you whether to continue or not with the paste. | | `defaultProfile` | _Required_ | String | PowerShell guid | Sets the default profile. Opens by typing Ctrl + T or by clicking the '+' icon. The guid of the desired default profile is used as the value. | | `initialCols` | _Required_ | Integer | `120` | The number of columns displayed in the window upon first load. | | `initialPosition` | Optional | String | `","` | The position of the top left corner of the window upon first load. On a system with multiple displays, these coordinates are relative to the top left of the primary display. If `launchMode` is set to `"maximized"`, the window will be maximized on the monitor specified by those coordinates. | | `initialRows` | _Required_ | Integer | `30` | The number of rows displayed in the window upon first load. | | `launchMode` | Optional | String | `default` | Defines whether the Terminal will launch as maximized or not. Possible values: `"default"`, `"maximized"` | -| `rowsToScroll` | Optional | Integer | `system` | The number of rows to scroll at a time with the mouse wheel. This will override the system setting if the value is not zero or "system". | | `theme` | _Required_ | String | `system` | Sets the theme of the application. Possible values: `"light"`, `"dark"`, `"system"` | | `showTerminalTitleInTitlebar` | _Required_ | Boolean | `true` | When set to `true`, titlebar displays the title of the selected tab. When set to `false`, titlebar displays "Windows Terminal". | | `showTabsInTitlebar` | Optional | Boolean | `true` | When set to `true`, the tabs are moved into the titlebar and the titlebar disappears. When set to `false`, the titlebar sits above the tabs. | diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 66a912a19bf..7232d65f133 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -1,6 +1,6 @@ { "$id": "https://github.com/microsoft/terminal/blob/master/doc/cascadia/profiles.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema#", "title": "Microsoft's Windows Terminal Settings Profile Schema", "definitions": { "KeyChordSegment": { @@ -54,8 +54,14 @@ "scrollUpPage", "splitPane", "switchToTab", + "toggleFocusMode", "toggleFullscreen", + "toggleRetroEffect", "find", + "setTabColor", + "openTabColorPicker", + "renameTab", + "commandPalette", "unbound" ], "type": "string" @@ -257,6 +263,22 @@ } ] }, + "SetTabColorAction": { + "description": "Arguments corresponding to a Set Tab Color Action", + "allOf": [ + { "$ref": "#/definitions/ShortcutAction" }, + { + "properties": { + "action": { "type": "string", "pattern": "setTabColor" }, + "color": { + "$ref": "#/definitions/Color", + "default": null, + "description": "If provided, will set the tab's color to the given value. If omitted, will reset the tab's color." + } + } + } + ] + }, "Keybinding": { "additionalProperties": false, "properties": { @@ -272,6 +294,7 @@ { "$ref": "#/definitions/ResizePaneAction" }, { "$ref": "#/definitions/SplitPaneAction" }, { "$ref": "#/definitions/OpenSettingsAction" }, + { "$ref": "#/definitions/SetTabColorAction" }, { "type": "null" } ] }, @@ -316,6 +339,16 @@ "description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard.", "type": "boolean" }, + "largePasteWarning": { + "default": true, + "description": "When set to true, trying to paste text with more than 5 KiB of characters will display a warning asking you whether to continue or not with the paste.", + "type": "boolean" + }, + "multiLinePasteWarning": { + "default": true, + "description": "When set to true, trying to paste text with a \"new line\" character will display a warning asking you whether to continue or not with the paste.", + "type": "boolean" + }, "defaultProfile": { "description": "Sets the default profile. Opens by clicking the \"+\" icon or typing the key binding assigned to \"newTab\".", "type": "string" @@ -364,10 +397,11 @@ }, "rowsToScroll": { "default": "system", - "description": "The number of rows to scroll at a time with the mouse wheel. This will override the system setting if the value is not zero or \"system\".", + "description": "This parameter once allowed you to override the systemwide \"choose how many lines to scroll at one time\" setting. It no longer does so.", "maximum": 999, "minimum": 0, - "type": [ "integer", "string" ] + "type": [ "integer", "string" ], + "deprecated": true }, "keybindings": { "description": "Properties are specific to each custom key binding.", diff --git a/doc/specs/#4993 - Keyboard Selection/Keyboard-Selection.md b/doc/specs/#4993 - Keyboard Selection/Keyboard-Selection.md index 7f95ccc979a..b8bcca7cf5a 100644 --- a/doc/specs/#4993 - Keyboard Selection/Keyboard-Selection.md +++ b/doc/specs/#4993 - Keyboard Selection/Keyboard-Selection.md @@ -1,7 +1,7 @@ --- author: Carlos Zamora @carlos-zamora created on: 2019-08-30 -last updated: 2020-06-23 +last updated: 2020-06-26 issue id: 715 --- @@ -49,17 +49,25 @@ The Terminal Core will need to expose a `MoveSelectionEndpoint()` function that - `enum Direction`: the direction that the selection anchor will attempt to move to. Possible values include `Up`, `Down`, `Left`, and `Right`. - `enum SelectionExpansionMode`: the selection expansion mode that the selection anchor will adhere to. Possible values include `Cell`, `Word`, `Viewport`, `Buffer`. +#### Moving by Cell For `SelectionExpansionMode = Cell`, the selection anchor will be updated according to the buffer's output pattern. For **horizontal movements**, the selection anchor will attempt to move left or right. If a viewport boundary is hit, the anchor will wrap appropriately (i.e.: hitting the left boundary moves it to the last cell of the line above it). For **vertical movements**, the selection anchor will attempt to move up or down. If a **viewport boundary** is hit and there is a scroll buffer, the anchor will move and scroll accordingly by a line. If a **buffer boundary** is hit, the anchor will not move. In this case, however, the event will still be considered handled. -For `SelectionExpansionMode = Word`, the selection anchor will also be updated according to the buffer's output pattern, as above. However, the selection will be updated in accordance with "chunk selection" (performing a double-click and dragging the mouse to expand the selection). For **horizontal movements**, the selection anchor will be updated according to the `_ExpandDoubleClickSelection` functions. The result must be saved to the anchor. As before, if a boundary is hit, the anchor will wrap appropriately. +**NOTE**: An important thing to handle properly in all cases is wide glyphs. The user should not be allowed to select a portion of a wide glyph; it should be all or none of it. When calling `_ExpandWideGlyphSelection` functions, the result must be saved to the anchor. + +#### Moving by Word +For `SelectionExpansionMode = Word`, the selection anchor will also be updated according to the buffer's output pattern, as above. However, the selection will be updated in accordance with "chunk selection" (performing a double-click and dragging the mouse to expand the selection). For **horizontal movements**, the selection anchor will be updated according to the `_ExpandDoubleClickSelection` functions. The result must be saved to the anchor. As before, if a boundary is hit, the anchor will wrap appropriately. See [Future Considerations](#FutureConsiderations) for how this will interact with line wrapping. For **vertical movements**, the movement is a little more complicated than before. The selection will still respond to buffer and viewport boundaries as before. If the user is trying to move up, the selection anchor will attempt to move up by one line, then selection will be expanded leftwards. Alternatively, if the user is trying to move down, the selection anchor will attempt to move down by one line, then the selection will be expanded rightwards. +#### Moving by Viewport For `SelectionExpansionMode = Viewport`, the selection anchor will be updated according to the viewport's height. Horizontal movements will be updated according to the viewport's width, thus resulting in the anchor being moved to the left/right boundary of the viewport. -**NOTE**: An important thing to handle properly in all cases is wide glyphs. The user should not be allowed to select a portion of a wide glyph; it should be all or none of it. When calling `_ExpandWideGlyphSelection` functions, the result must be saved to the anchor. +#### Moving by Buffer + +For `SelectionExpansionMode = Buffer`, the selection anchor will be moved to the beginning or end of all the text within the buffer. If moving up or left, set the position to 0,0 (the origin of the buffer). If moving down or right, set the position to the last character in the buffer. + **NOTE**: In all cases, horizontal movements attempting to move past the left/right viewport boundaries result in a wrap. Vertical movements attempting to move past the top/bottom viewport boundaries will scroll such that the selection is at the edge of the screen. Vertical movements attempting to move past the top/bottom buffer boundaries will be clamped to be within buffer boundaries. @@ -128,9 +136,9 @@ Thanks to Keybinding Args, there will only be 2 new commands that need to be add | Action | Keybinding Args | Description | |--|--|--| | `moveSelectionPoint` | | If a selection exists, moves the last selection anchor. -| | `Enum direction { up, down, left, right}` | The direction the selection will be moved in. | -| | `Enum expansionMode { cell, word, viewport, buffer }` | The context for which to move the selection anchor to. (defaults to `cell`) -| `selectEntireBuffer` | | Select the entire text buffer. +| | `Enum direction { up, down, left, right }` | The direction the selection will be moved in. | +| | `Enum expandBy { cell, word, page, all }` | The context for which to move the selection anchor to. (defaults to `cell`) +| `selectAll` | | Select the entire text buffer. | `markMode` | | Cycle the selection point targeted by `moveSelectionPoint`. If no selection exists, a selection is created at the cursor. | | `toggleBlockSelection` | | Transform the existing selection between a block selection and a line selection. | @@ -144,26 +152,27 @@ By default, the following keybindings will be set: { "command": { "action": "moveSelectionPoint", "direction": "right" }, "keys": "shift+right" }, // Word Selection -{ "command": { "action": "moveSelectionPoint", "direction": "left", "expansionMode": "word" }, "keys": "ctrl+shift+left" }, -{ "command": { "action": "moveSelectionPoint", "direction": "right", "expansionMode": "word" }, "keys": "ctrl+shift+right" }, +{ "command": { "action": "moveSelectionPoint", "direction": "left", "expandBy": "word" }, "keys": "ctrl+shift+left" }, +{ "command": { "action": "moveSelectionPoint", "direction": "right", "expandBy": "word" }, "keys": "ctrl+shift+right" }, // Viewport Selection -{ "command": { "action": "moveSelectionPoint", "direction": "left", "expansionMode": "viewport" }, "keys": "shift+home" }, -{ "command": { "action": "moveSelectionPoint", "direction": "right", "expansionMode": "viewport" }, "keys": "shift+end" }, -{ "command": { "action": "moveSelectionPoint", "direction": "up", "expansionMode": "viewport" }, "keys": "shift+pgup" }, -{ "command": { "action": "moveSelectionPoint", "direction": "down", "expansionMode": "viewport" }, "keys": "shift+pgdn" }, +{ "command": { "action": "moveSelectionPoint", "direction": "left", "expandBy": "page" }, "keys": "shift+home" }, +{ "command": { "action": "moveSelectionPoint", "direction": "right", "expandBy": "page" }, "keys": "shift+end" }, +{ "command": { "action": "moveSelectionPoint", "direction": "up", "expandBy": "page" }, "keys": "shift+pgup" }, +{ "command": { "action": "moveSelectionPoint", "direction": "down", "expandBy": "page" }, "keys": "shift+pgdn" }, // Buffer Corner Selection -{ "command": { "action": "moveSelectionPoint", "direction": "up", "expansionMode": "buffer" }, "keys": "ctrl+shift+home" }, -{ "command": { "action": "moveSelectionPoint", "direction": "down", "expansionMode": "buffer" }, "keys": "ctrl+shift+end" }, +{ "command": { "action": "moveSelectionPoint", "direction": "up", "expandBy": "all" }, "keys": "ctrl+shift+home" }, +{ "command": { "action": "moveSelectionPoint", "direction": "down", "expandBy": "all" }, "keys": "ctrl+shift+end" }, // Select All -{ "command": "selectEntireBuffer", "keys": "ctrl+shift+a" }, +{ "command": "selectAll", "keys": "ctrl+shift+a" }, // Mark Mode { "command": "markMode", "keys": "ctrl+shift+m" }, { "command": "toggleBlockSelection", "keys": "alt+shift+m" }, ``` +These are in accordance with ConHost's keyboard selection model. ## Capabilities @@ -195,7 +204,14 @@ The settings model makes all of these features easy to disable, if the user wish ## Future considerations -None +### Grapheme Clusters +When grapheme cluster support is inevitably added to the Text Buffer, moving by "cell" is expected to move by "character" or "cluster". This is similar to how wide glyphs are handled today. Either all of it is selected, or none of it. + +### Word Selection Wrap +At the time of writing this spec, expanding or moving by word is interrupted by the beginning or end of the line, regardless of the wrap flag being set. In the future, selection and the accessibility models will respect the wrap flag on the text buffer. + +### Contextual Keybindings +This feature introduces a large number of keybindings that only work if a selection is active. Currently, key bindings cannot be bound to a context, so if a user binds `moveSelectionPoint` to `shift+up` and there is no selection, `shift+up` is sent directly to the Terminal. In the future, a `context` key could be added to new bindings to get around this problem. That way, users could bind other actions to `shift+up` to run specifically when a selection is not active. ## Resources diff --git a/doc/specs/#980 - SnapOnOutput.md b/doc/specs/#980 - SnapOnOutput.md new file mode 100644 index 00000000000..3d48cf45e9c --- /dev/null +++ b/doc/specs/#980 - SnapOnOutput.md @@ -0,0 +1,122 @@ +--- +author: Carlos Zamora @carlos-zamora +created on: 2019-08-22 +last updated: 2020-07-06 +issue id: 980 +--- + +# Snap On Output + +## Abstract + +The goal of this change is to determine the Terminal's scroll response to newly generated output. + +Currently, new output causes the Terminal to always scroll to it. Some users want to be able to scroll through the buffer without interruptions. + +## Inspiration + +In ConHost, a selection causes the active process to be completely paused. When the selection is removed, the process continues. + +Typical Unix terminals work differently. Rather than disabling the output, they disable the automatic scrolling. This allows the user to continue to see more output by choice. + +## Solution Design + +By default, the viewport will scroll to new output if the following conditions are met: +- no selection is active +- the viewport is at the "virtual bottom" (the bottom of the scroll history) + +This behavior will not be configurable. If the user wants the viewport to stop autoscrolling, the user will simply create a selection or scroll any distance above the virtual bottom. Conversely, if the user wants the viewport to automatically scroll, the user must scroll to the bottom. Scrolling to the bottom is most easily achieved using the `snapOnInput` functionality. + +Alternative solutions were considered and are recorded below. These solutions may be revisited if users desire an additional level of configurability. + +Researching other terminal emulators has shown that this behavior is not configurable. + +## Alternative Solutions + +### Solution 1: `snapOnOutput` profile setting - enum flags +`SnapOnOutput` will be a profile-level `ICoreSettings` setting of type enum or enum array. It can be set to one or multiple of the following values: +- `never`: new output does not cause the viewport to update to the bottom of the scroll region +- `noSelection`: new output causes the viewport to update to the bottom of the scroll region **IF** no selection is active +- `atBottom`: new output causes the viewport to update **IF** the viewport is already at the virtual bottom +- `always`: new output causes the viewport to update to the bottom of the scroll region + +The `TerminalCore` is responsible for moving the viewport on a scroll event. All of the logic for this feature should be handled here. + +A new private enum array `_snapOnOutput` will be introduced to save which of these settings are included. The `_NotifyScrollEvent()` calls (and nearby code) will be surrounded by conditional checks for the enums above. This allows it to be used to determine if the viewport should update given a specific situation. + +The `snapOnOutput` setting is introduced as a profile setting to match `snapOnInput`. + +The default `snapOnOutput` value will be `[ "noSelection", "atBottom" ]`. + +When an enum array is defined in the settings, it will be interpreted using boolean logic. The following scenarios will be invalid using the FlagMapper: +- `[ "always", "atBottom" ]` +- `[ "never", "atBottom" ]` + +### Solution 2: `scrollLock` keybinding action + +A `scrollLock` keybinding action would toggle automatically scrolling to new output. + +**NOTE**: This can be easily confused with the ScrollLock key. Researching the use of the ScrollLock key has shown that programs rarely use this key. In most apps, pressing the ScrollLock key does not actually prevent scrolling the application. Additionally, finding a way to bing the `scrollLock` action to the ScrollLock key would be difficult. A physical keyboard may not necessarily have a ScrollLock key. Also, we would have to poll for the internal state of "is the scroll lock key enabled", which may change while the user is not necessarily using Terminal. + +The introduction of a `scrollLock` action would require a visual indicator for the user to know when scrolling has been disabled. However, this introduces a number of problems: +- if the indicator is persistent, it may block the view +- if the indicator is not persistent, the user may be unaware of being in a state where scrolling doesn't work properly + +**Additionally relevant research**: +- In Unix consoles, ctrl+s and ctrl+q freeze and unfreeze output respectively. However, this is a feature that is implemented outside of the scope for Terminal. Other shells like PowerShell do not have this feature, for example. There, ctrl+s does a 'Forward Search History' instead. +- Additionally, there is a Pause key that pauses the output in the conhost console. Pressing any other key will resume scrolling. + + +## Capabilities + +### Accessibility + +N/A + +### Security + +N/A + +### Reliability + +N/A + +### Compatibility + +N/A + +### Performance, Power, and Efficiency + +N/A + +## Potential Issues + +### Circling the buffer +If the text buffer fills up, the text buffer begins 'circling'. This means that new output shifts lines of the buffer up to make space. In a case like this, if `snapOnOutput` is set to `never`, the viewport should actually scroll up to keep the same content on the viewport. + +In the event that the buffer is circling and the viewport has been moved to the top of the buffer, that content of the buffer is now lost (as the 'Infinite Scrollback' feature does not exist or is disabled). At that point, the viewport will remain at the top of the buffer and the new output will push old output out of the buffer. + +### Infinite Scrollback +See **Future considerations** > **Infinite Scrollback**. + +## Future considerations + +### Extensibility +The introduction of `enum SnapOnOutput` allows for this feature to be enabled/disabled in more complex scenarios. A potential extension would be to introduce a new UI element or keybinding to toggle this feature. + +### Infinite Scrollback +At the time of introducing this, the infinite scrollback feature is not supported. This means that the buffer saves the history up to the `historySize` amount of lines. When infinite scrollback is introduced, the buffer needs to change its own contents to allow the user to scroll beyond the `historySize`. With infinite scrollback enabled and the mutable viewport **NOT** snapping to new output, the `TerminalCore` needs to keep track of... +- what contents are currently visible to the user (in the current location of the mutable viewport) +- how to respond to a user's action of changing the location of the mutable viewport (i.e.: snapOnInput, scroll up/down) + +### Private Mode Escape Sequences +There are a couple of private mode escape sequences that some terminals use to control this kind of thing. DECSET 1010, for example, snaps the viewport to the bottom on output, whereas DECSET 1011 spans the viewport to the bottom on a keypress. + +DECSET 1010 should set the `SnapOnOutput` value via a Terminal API. +DECSET 1011 should set the `SnapOnInput` value via a Terminal API. + +## Resources + +[GH#980](https://github.com/microsoft/terminal/issues/980) +[DECSET 1010](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Functions-using-CSI-_-ordered-by-the-final-character-lparen-s-rparen:CSI-?-Pm-h:Ps-=-1-0-1-0.1F79) +[DECSET 1011](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Functions-using-CSI-_-ordered-by-the-final-character-lparen-s-rparen:CSI-?-Pm-h:Ps-=-1-0-1-1.1F7A) diff --git a/oss/fmt/cgmanifest.json b/oss/fmt/cgmanifest.json index a8148d03abc..1a8c5d01b14 100644 --- a/oss/fmt/cgmanifest.json +++ b/oss/fmt/cgmanifest.json @@ -4,7 +4,7 @@ "type": "git", "git": { "repositoryUrl": "https://github.com/fmtlib/fmt", - "commitHash": "9bdd1596cef1b57b9556f8bef32dc4a32322ef3e" + "commitHash": "f19b1a521ee8b606dedcadfda69fd10ddf882753" } } } diff --git a/oss/fmt/include/fmt/chrono.h b/oss/fmt/include/fmt/chrono.h index 421d464ad8a..e70b8053a61 100644 --- a/oss/fmt/include/fmt/chrono.h +++ b/oss/fmt/include/fmt/chrono.h @@ -48,7 +48,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { // From fits in To without any problem. } else { // From does not always fit in To, resort to a dynamic check. - if (from < T::min() || from > T::max()) { + if (from < (T::min)() || from > (T::max)()) { // outside range. ec = 1; return {}; @@ -74,7 +74,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { if (F::is_signed && !T::is_signed) { // From may be negative, not allowed! - if (fmt::internal::is_negative(from)) { + if (fmt::detail::is_negative(from)) { ec = 1; return {}; } @@ -84,7 +84,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { // yes, From always fits in To. } else { // from may not fit in To, we have to do a dynamic check - if (from > static_cast(T::max())) { + if (from > static_cast((T::max)())) { ec = 1; return {}; } @@ -97,7 +97,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { // yes, From always fits in To. } else { // from may not fit in To, we have to do a dynamic check - if (from > static_cast(T::max())) { + if (from > static_cast((T::max)())) { // outside range. ec = 1; return {}; @@ -141,7 +141,7 @@ FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { // catch the only happy case if (std::isfinite(from)) { - if (from >= T::lowest() && from <= T::max()) { + if (from >= T::lowest() && from <= (T::max)()) { return static_cast(from); } // not within range. @@ -195,12 +195,13 @@ To safe_duration_cast(std::chrono::duration from, } // multiply with Factor::num without overflow or underflow if (Factor::num != 1) { - const auto max1 = internal::max_value() / Factor::num; + const auto max1 = detail::max_value() / Factor::num; if (count > max1) { ec = 1; return {}; } - const auto min1 = std::numeric_limits::min() / Factor::num; + const auto min1 = + (std::numeric_limits::min)() / Factor::num; if (count < min1) { ec = 1; return {}; @@ -269,7 +270,7 @@ To safe_duration_cast(std::chrono::duration from, // multiply with Factor::num without overflow or underflow if (Factor::num != 1) { - constexpr auto max1 = internal::max_value() / + constexpr auto max1 = detail::max_value() / static_cast(Factor::num); if (count > max1) { ec = 1; @@ -306,12 +307,12 @@ To safe_duration_cast(std::chrono::duration from, // Usage: f FMT_NOMACRO() #define FMT_NOMACRO -namespace internal { +namespace detail { inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); } inline null<> localtime_s(...) { return null<>(); } inline null<> gmtime_r(...) { return null<>(); } inline null<> gmtime_s(...) { return null<>(); } -} // namespace internal +} // namespace detail // Thread-safe replacement for std::localtime inline std::tm localtime(std::time_t time) { @@ -322,22 +323,22 @@ inline std::tm localtime(std::time_t time) { dispatcher(std::time_t t) : time_(t) {} bool run() { - using namespace fmt::internal; + using namespace fmt::detail; return handle(localtime_r(&time_, &tm_)); } bool handle(std::tm* tm) { return tm != nullptr; } - bool handle(internal::null<>) { - using namespace fmt::internal; + bool handle(detail::null<>) { + using namespace fmt::detail; return fallback(localtime_s(&tm_, &time_)); } bool fallback(int res) { return res == 0; } #if !FMT_MSC_VER - bool fallback(internal::null<>) { - using namespace fmt::internal; + bool fallback(detail::null<>) { + using namespace fmt::detail; std::tm* tm = std::localtime(&time_); if (tm) tm_ = *tm; return tm != nullptr; @@ -359,21 +360,21 @@ inline std::tm gmtime(std::time_t time) { dispatcher(std::time_t t) : time_(t) {} bool run() { - using namespace fmt::internal; + using namespace fmt::detail; return handle(gmtime_r(&time_, &tm_)); } bool handle(std::tm* tm) { return tm != nullptr; } - bool handle(internal::null<>) { - using namespace fmt::internal; + bool handle(detail::null<>) { + using namespace fmt::detail; return fallback(gmtime_s(&tm_, &time_)); } bool fallback(int res) { return res == 0; } #if !FMT_MSC_VER - bool fallback(internal::null<>) { + bool fallback(detail::null<>) { std::tm* tm = std::gmtime(&time_); if (tm) tm_ = *tm; return tm != nullptr; @@ -386,17 +387,17 @@ inline std::tm gmtime(std::time_t time) { return gt.tm_; } -namespace internal { -inline std::size_t strftime(char* str, std::size_t count, const char* format, - const std::tm* time) { +namespace detail { +inline size_t strftime(char* str, size_t count, const char* format, + const std::tm* time) { return std::strftime(str, count, format, time); } -inline std::size_t strftime(wchar_t* str, std::size_t count, - const wchar_t* format, const std::tm* time) { +inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format, + const std::tm* time) { return std::wcsftime(str, count, format, time); } -} // namespace internal +} // namespace detail template struct formatter { template @@ -405,7 +406,7 @@ template struct formatter { if (it != ctx.end() && *it == ':') ++it; auto end = it; while (end != ctx.end() && *end != '}') ++end; - tm_format.reserve(internal::to_unsigned(end - it + 1)); + tm_format.reserve(detail::to_unsigned(end - it + 1)); tm_format.append(it, end); tm_format.push_back('\0'); return end; @@ -414,11 +415,10 @@ template struct formatter { template auto format(const std::tm& tm, FormatContext& ctx) -> decltype(ctx.out()) { basic_memory_buffer buf; - std::size_t start = buf.size(); + size_t start = buf.size(); for (;;) { - std::size_t size = buf.capacity() - start; - std::size_t count = - internal::strftime(&buf[start], size, &tm_format[0], &tm); + size_t size = buf.capacity() - start; + size_t count = detail::strftime(&buf[start], size, &tm_format[0], &tm); if (count != 0) { buf.resize(start + count); break; @@ -430,7 +430,7 @@ template struct formatter { // https://github.com/fmtlib/fmt/issues/367 break; } - const std::size_t MIN_GROWTH = 10; + const size_t MIN_GROWTH = 10; buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); } return std::copy(buf.begin(), buf.end(), ctx.out()); @@ -439,7 +439,7 @@ template struct formatter { basic_memory_buffer tm_format; }; -namespace internal { +namespace detail { template FMT_CONSTEXPR const char* get_units() { return nullptr; } @@ -768,19 +768,25 @@ OutputIt format_duration_value(OutputIt out, Rep val, int precision) { return format_to(out, std::is_floating_point::value ? fp_f : format, val); } +template +OutputIt copy_unit(string_view unit, OutputIt out, Char) { + return std::copy(unit.begin(), unit.end(), out); +} + +template +OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) { + // This works when wchar_t is UTF-32 because units only contain characters + // that have the same representation in UTF-16 and UTF-32. + utf8_to_utf16 u(unit); + return std::copy(u.c_str(), u.c_str() + u.size(), out); +} template OutputIt format_duration_unit(OutputIt out) { - if (const char* unit = get_units()) { - string_view s(unit); - if (const_check(std::is_same())) { - utf8_to_utf16 u(s); - return std::copy(u.c_str(), u.c_str() + u.size(), out); - } - return std::copy(s.begin(), s.end(), out); - } + if (const char* unit = get_units()) + return copy_unit(string_view(unit), out, Char()); const Char num_f[] = {'[', '{', '}', ']', 's', 0}; - if (Period::den == 1) return format_to(out, num_f, Period::num); + if (const_check(Period::den == 1)) return format_to(out, num_f, Period::num); const Char num_def_f[] = {'[', '{', '}', '/', '{', '}', ']', 's', 0}; return format_to(out, num_def_f, Period::num, Period::den); } @@ -874,9 +880,9 @@ struct chrono_formatter { if (isnan(value)) return write_nan(); uint32_or_64_or_128_t n = to_unsigned(to_nonnegative_int(value, max_value())); - int num_digits = internal::count_digits(n); + int num_digits = detail::count_digits(n); if (width > num_digits) out = std::fill_n(out, width - num_digits, '0'); - out = format_decimal(out, n, num_digits); + out = format_decimal(out, n, num_digits).end; } void write_nan() { std::copy_n("nan", 3, out); } @@ -1004,14 +1010,14 @@ struct chrono_formatter { out = format_duration_unit(out); } }; -} // namespace internal +} // namespace detail template struct formatter, Char> { private: basic_format_specs specs; int precision; - using arg_ref_type = internal::arg_ref; + using arg_ref_type = detail::arg_ref; arg_ref_type width_ref; arg_ref_type precision_ref; mutable basic_string_view format_str; @@ -1032,7 +1038,7 @@ struct formatter, Char> { return arg_ref_type(arg_id); } - FMT_CONSTEXPR arg_ref_type make_arg_ref(internal::auto_id) { + FMT_CONSTEXPR arg_ref_type make_arg_ref(detail::auto_id) { return arg_ref_type(context.next_arg_id()); } @@ -1062,17 +1068,17 @@ struct formatter, Char> { auto begin = ctx.begin(), end = ctx.end(); if (begin == end || *begin == '}') return {begin, begin}; spec_handler handler{*this, ctx, format_str}; - begin = internal::parse_align(begin, end, handler); + begin = detail::parse_align(begin, end, handler); if (begin == end) return {begin, begin}; - begin = internal::parse_width(begin, end, handler); + begin = detail::parse_width(begin, end, handler); if (begin == end) return {begin, begin}; if (*begin == '.') { if (std::is_floating_point::value) - begin = internal::parse_precision(begin, end, handler); + begin = detail::parse_precision(begin, end, handler); else handler.on_error("precision not allowed for this argument type"); } - end = parse_chrono_format(begin, end, internal::chrono_format_checker()); + end = parse_chrono_format(begin, end, detail::chrono_format_checker()); return {begin, end}; } @@ -1083,7 +1089,7 @@ struct formatter, Char> { -> decltype(ctx.begin()) { auto range = do_parse(ctx); format_str = basic_string_view( - &*range.begin, internal::to_unsigned(range.end - range.begin)); + &*range.begin, detail::to_unsigned(range.end - range.begin)); return range.end; } @@ -1094,23 +1100,21 @@ struct formatter, Char> { // is not specified. basic_memory_buffer buf; auto out = std::back_inserter(buf); - using range = internal::output_range; - internal::basic_writer w(range(ctx.out())); - internal::handle_dynamic_spec(specs.width, - width_ref, ctx); - internal::handle_dynamic_spec( - precision, precision_ref, ctx); + detail::handle_dynamic_spec(specs.width, width_ref, + ctx); + detail::handle_dynamic_spec(precision, + precision_ref, ctx); if (begin == end || *begin == '}') { - out = internal::format_duration_value(out, d.count(), precision); - internal::format_duration_unit(out); + out = detail::format_duration_value(out, d.count(), precision); + detail::format_duration_unit(out); } else { - internal::chrono_formatter f( + detail::chrono_formatter f( ctx, out, d); f.precision = precision; parse_chrono_format(begin, end, f); } - w.write(buf.data(), buf.size(), specs); - return w.out(); + return detail::write( + ctx.out(), basic_string_view(buf.data(), buf.size()), specs); } }; diff --git a/oss/fmt/include/fmt/color.h b/oss/fmt/include/fmt/color.h index 96d9ab6b438..b65f892afc0 100644 --- a/oss/fmt/include/fmt/color.h +++ b/oss/fmt/include/fmt/color.h @@ -198,7 +198,7 @@ struct rgb { uint8_t b; }; -namespace internal { +namespace detail { // color is a struct of either a rgb color or a terminal color. struct color_type { @@ -221,7 +221,7 @@ struct color_type { uint32_t rgb_color; } value; }; -} // namespace internal +} // namespace detail // Experimental text formatting support. class text_style { @@ -298,11 +298,11 @@ class text_style { FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT { return static_cast(ems) != 0; } - FMT_CONSTEXPR internal::color_type get_foreground() const FMT_NOEXCEPT { + FMT_CONSTEXPR detail::color_type get_foreground() const FMT_NOEXCEPT { FMT_ASSERT(has_foreground(), "no foreground specified for this style"); return foreground_color; } - FMT_CONSTEXPR internal::color_type get_background() const FMT_NOEXCEPT { + FMT_CONSTEXPR detail::color_type get_background() const FMT_NOEXCEPT { FMT_ASSERT(has_background(), "no background specified for this style"); return background_color; } @@ -313,7 +313,7 @@ class text_style { private: FMT_CONSTEXPR text_style(bool is_foreground, - internal::color_type text_color) FMT_NOEXCEPT + detail::color_type text_color) FMT_NOEXCEPT : set_foreground_color(), set_background_color(), ems() { @@ -326,23 +326,23 @@ class text_style { } } - friend FMT_CONSTEXPR_DECL text_style fg(internal::color_type foreground) + friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground) FMT_NOEXCEPT; - friend FMT_CONSTEXPR_DECL text_style bg(internal::color_type background) + friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background) FMT_NOEXCEPT; - internal::color_type foreground_color; - internal::color_type background_color; + detail::color_type foreground_color; + detail::color_type background_color; bool set_foreground_color; bool set_background_color; emphasis ems; }; -FMT_CONSTEXPR text_style fg(internal::color_type foreground) FMT_NOEXCEPT { +FMT_CONSTEXPR text_style fg(detail::color_type foreground) FMT_NOEXCEPT { return text_style(/*is_foreground=*/true, foreground); } -FMT_CONSTEXPR text_style bg(internal::color_type background) FMT_NOEXCEPT { +FMT_CONSTEXPR text_style bg(detail::color_type background) FMT_NOEXCEPT { return text_style(/*is_foreground=*/false, background); } @@ -350,21 +350,21 @@ FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT { return text_style(lhs) | rhs; } -namespace internal { +namespace detail { template struct ansi_color_escape { - FMT_CONSTEXPR ansi_color_escape(internal::color_type text_color, + FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color, const char* esc) FMT_NOEXCEPT { // If we have a terminal color, we need to output another escape code // sequence. if (!text_color.is_rgb) { - bool is_background = esc == internal::data::background_color; + bool is_background = esc == detail::data::background_color; uint32_t value = text_color.value.term_color; // Background ASCII codes are the same as the foreground ones but with // 10 more. if (is_background) value += 10u; - std::size_t index = 0; + size_t index = 0; buffer[index++] = static_cast('\x1b'); buffer[index++] = static_cast('['); @@ -398,7 +398,7 @@ template struct ansi_color_escape { if (em_bits & static_cast(emphasis::strikethrough)) em_codes[3] = 9; - std::size_t index = 0; + size_t index = 0; for (int i = 0; i < 4; ++i) { if (!em_codes[i]) continue; buffer[index++] = static_cast('\x1b'); @@ -429,14 +429,14 @@ template struct ansi_color_escape { template FMT_CONSTEXPR ansi_color_escape make_foreground_color( - internal::color_type foreground) FMT_NOEXCEPT { - return ansi_color_escape(foreground, internal::data::foreground_color); + detail::color_type foreground) FMT_NOEXCEPT { + return ansi_color_escape(foreground, detail::data::foreground_color); } template FMT_CONSTEXPR ansi_color_escape make_background_color( - internal::color_type background) FMT_NOEXCEPT { - return ansi_color_escape(background, internal::data::background_color); + detail::color_type background) FMT_NOEXCEPT { + return ansi_color_escape(background, detail::data::background_color); } template @@ -455,11 +455,11 @@ inline void fputs(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT { } template inline void reset_color(FILE* stream) FMT_NOEXCEPT { - fputs(internal::data::reset_color, stream); + fputs(detail::data::reset_color, stream); } template <> inline void reset_color(FILE* stream) FMT_NOEXCEPT { - fputs(internal::data::wreset_color, stream); + fputs(detail::data::wreset_color, stream); } template @@ -476,33 +476,31 @@ void vformat_to(basic_memory_buffer& buf, const text_style& ts, bool has_style = false; if (ts.has_emphasis()) { has_style = true; - auto emphasis = internal::make_emphasis(ts.get_emphasis()); + auto emphasis = detail::make_emphasis(ts.get_emphasis()); buf.append(emphasis.begin(), emphasis.end()); } if (ts.has_foreground()) { has_style = true; - auto foreground = - internal::make_foreground_color(ts.get_foreground()); + auto foreground = detail::make_foreground_color(ts.get_foreground()); buf.append(foreground.begin(), foreground.end()); } if (ts.has_background()) { has_style = true; - auto background = - internal::make_background_color(ts.get_background()); + auto background = detail::make_background_color(ts.get_background()); buf.append(background.begin(), background.end()); } - internal::vformat_to(buf, format_str, args); - if (has_style) internal::reset_color(buf); + detail::vformat_to(buf, format_str, args); + if (has_style) detail::reset_color(buf); } -} // namespace internal +} // namespace detail template > void vprint(std::FILE* f, const text_style& ts, const S& format, basic_format_args> args) { basic_memory_buffer buf; - internal::vformat_to(buf, ts, to_string_view(format), args); + detail::vformat_to(buf, ts, to_string_view(format), args); buf.push_back(Char(0)); - internal::fputs(buf.data(), f); + detail::fputs(buf.data(), f); } /** @@ -513,10 +511,10 @@ void vprint(std::FILE* f, const text_style& ts, const S& format, "Elapsed time: {0:.2f} seconds", 1.23); */ template ::value)> + FMT_ENABLE_IF(detail::is_string::value)> void print(std::FILE* f, const text_style& ts, const S& format_str, const Args&... args) { - internal::check_format_string(format_str); + detail::check_format_string(format_str); using context = buffer_context>; format_arg_store as{args...}; vprint(f, ts, format_str, basic_format_args(as)); @@ -530,7 +528,7 @@ void print(std::FILE* f, const text_style& ts, const S& format_str, "Elapsed time: {0:.2f} seconds", 1.23); */ template ::value)> + FMT_ENABLE_IF(detail::is_string::value)> void print(const text_style& ts, const S& format_str, const Args&... args) { return print(stdout, ts, format_str, args...); } @@ -540,7 +538,7 @@ inline std::basic_string vformat( const text_style& ts, const S& format_str, basic_format_args>> args) { basic_memory_buffer buf; - internal::vformat_to(buf, ts, to_string_view(format_str), args); + detail::vformat_to(buf, ts, to_string_view(format_str), args); return fmt::to_string(buf); } @@ -560,7 +558,7 @@ template > inline std::basic_string format(const text_style& ts, const S& format_str, const Args&... args) { return vformat(ts, to_string_view(format_str), - internal::make_args_checked(format_str, args...)); + detail::make_args_checked(format_str, args...)); } FMT_END_NAMESPACE diff --git a/oss/fmt/include/fmt/compile.h b/oss/fmt/include/fmt/compile.h index e4b12f349e9..d7e6449ebbf 100644 --- a/oss/fmt/include/fmt/compile.h +++ b/oss/fmt/include/fmt/compile.h @@ -13,7 +13,33 @@ #include "format.h" FMT_BEGIN_NAMESPACE -namespace internal { +namespace detail { + +// A compile-time string which is compiled into fast formatting code. +class compiled_string {}; + +template +struct is_compiled_string : std::is_base_of {}; + +/** + \rst + Converts a string literal *s* into a format string that will be parsed at + compile time and converted into efficient formatting code. Requires C++17 + ``constexpr if`` compiler support. + + **Example**:: + + // Converts 42 into std::string using the most efficient method and no + // runtime format string processing. + std::string s = fmt::format(FMT_COMPILE("{}"), 42); + \endrst + */ +#define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::detail::compiled_string) + +template +const T& first(const T& value, const Tail&...) { + return value; +} // Part of a compiled format string. It can be either literal text or a // replacement field. @@ -62,13 +88,15 @@ template struct part_counter { if (begin != end) ++num_parts; } - FMT_CONSTEXPR void on_arg_id() { ++num_parts; } - FMT_CONSTEXPR void on_arg_id(int) { ++num_parts; } - FMT_CONSTEXPR void on_arg_id(basic_string_view) { ++num_parts; } + FMT_CONSTEXPR int on_arg_id() { return ++num_parts, 0; } + FMT_CONSTEXPR int on_arg_id(int) { return ++num_parts, 0; } + FMT_CONSTEXPR int on_arg_id(basic_string_view) { + return ++num_parts, 0; + } - FMT_CONSTEXPR void on_replacement_field(const Char*) {} + FMT_CONSTEXPR void on_replacement_field(int, const Char*) {} - FMT_CONSTEXPR const Char* on_format_specs(const Char* begin, + FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin, const Char* end) { // Find the matching brace. unsigned brace_counter = 0; @@ -116,25 +144,28 @@ class format_string_compiler : public error_handler { handler_(part::make_text({begin, to_unsigned(end - begin)})); } - FMT_CONSTEXPR void on_arg_id() { + FMT_CONSTEXPR int on_arg_id() { part_ = part::make_arg_index(parse_context_.next_arg_id()); + return 0; } - FMT_CONSTEXPR void on_arg_id(int id) { + FMT_CONSTEXPR int on_arg_id(int id) { parse_context_.check_arg_id(id); part_ = part::make_arg_index(id); + return 0; } - FMT_CONSTEXPR void on_arg_id(basic_string_view id) { + FMT_CONSTEXPR int on_arg_id(basic_string_view id) { part_ = part::make_arg_name(id); + return 0; } - FMT_CONSTEXPR void on_replacement_field(const Char* ptr) { + FMT_CONSTEXPR void on_replacement_field(int, const Char* ptr) { part_.arg_id_end = ptr; handler_(part_); } - FMT_CONSTEXPR const Char* on_format_specs(const Char* begin, + FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin, const Char* end) { auto repl = typename part::replacement(); dynamic_specs_handler> handler( @@ -160,23 +191,24 @@ FMT_CONSTEXPR void compile_format_string(basic_string_view format_str, format_string_compiler(format_str, handler)); } -template +template void format_arg( - basic_format_parse_context& parse_ctx, + basic_format_parse_context& parse_ctx, Context& ctx, Id arg_id) { - ctx.advance_to( - visit_format_arg(arg_formatter(ctx, &parse_ctx), ctx.arg(arg_id))); + ctx.advance_to(visit_format_arg( + arg_formatter(ctx, &parse_ctx), + ctx.arg(arg_id))); } // vformat_to is defined in a subnamespace to prevent ADL. namespace cf { -template -auto vformat_to(Range out, CompiledFormat& cf, basic_format_args args) - -> typename Context::iterator { +template +auto vformat_to(OutputIt out, CompiledFormat& cf, + basic_format_args args) -> typename Context::iterator { using char_type = typename Context::char_type; basic_format_parse_context parse_ctx( to_string_view(cf.format_str_)); - Context ctx(out.begin(), args); + Context ctx(out, args); const auto& parts = cf.parts(); for (auto part_it = std::begin(parts); part_it != std::end(parts); @@ -197,12 +229,12 @@ auto vformat_to(Range out, CompiledFormat& cf, basic_format_args args) case format_part_t::kind::arg_index: advance_to(parse_ctx, part.arg_id_end); - internal::format_arg(parse_ctx, ctx, value.arg_index); + detail::format_arg(parse_ctx, ctx, value.arg_index); break; case format_part_t::kind::arg_name: advance_to(parse_ctx, part.arg_id_end); - internal::format_arg(parse_ctx, ctx, value.str); + detail::format_arg(parse_ctx, ctx, value.str); break; case format_part_t::kind::replacement: { @@ -226,7 +258,9 @@ auto vformat_to(Range out, CompiledFormat& cf, basic_format_args args) advance_to(parse_ctx, part.arg_id_end); ctx.advance_to( - visit_format_arg(arg_formatter(ctx, nullptr, &specs), arg)); + visit_format_arg(arg_formatter( + ctx, nullptr, &specs), + arg)); break; } } @@ -240,7 +274,7 @@ struct basic_compiled_format {}; template struct compiled_format_base : basic_compiled_format { using char_type = char_t; - using parts_container = std::vector>; + using parts_container = std::vector>; parts_container compiled_parts; @@ -305,7 +339,7 @@ struct compiled_format_base::value>> const parts_container& parts() const { static FMT_CONSTEXPR_DECL const auto compiled_parts = compile_to_parts( - internal::to_string_view(S())); + detail::to_string_view(S())); return compiled_parts.data; } }; @@ -318,8 +352,8 @@ class compiled_format : private compiled_format_base { private: basic_string_view format_str_; - template - friend auto cf::vformat_to(Range out, CompiledFormat& cf, + template + friend auto cf::vformat_to(OutputIt out, CompiledFormat& cf, basic_format_args args) -> typename Context::iterator; @@ -359,8 +393,7 @@ template struct text { template OutputIt format(OutputIt out, const Args&...) const { - // TODO: reserve - return copy_str(data.begin(), data.end(), out); + return write(out, data); } }; @@ -373,33 +406,6 @@ constexpr text make_text(basic_string_view s, size_t pos, return {{&s[pos], size}}; } -template , int> = 0> -OutputIt format_default(OutputIt out, T value) { - // TODO: reserve - format_int fi(value); - return std::copy(fi.data(), fi.data() + fi.size(), out); -} - -template -OutputIt format_default(OutputIt out, double value) { - writer w(out); - w.write(value); - return w.out(); -} - -template -OutputIt format_default(OutputIt out, Char value) { - *out++ = value; - return out; -} - -template -OutputIt format_default(OutputIt out, const Char* value) { - auto length = std::char_traits::length(value); - return copy_str(value, value + length, out); -} - // A replacement field that refers to argument N. template struct field { using char_type = Char; @@ -408,13 +414,30 @@ template struct field { OutputIt format(OutputIt out, const Args&... args) const { // This ensures that the argument type is convertile to `const T&`. const T& arg = get(args...); - return format_default(out, arg); + return write(out, arg); } }; template struct is_compiled_format> : std::true_type {}; +// A replacement field that refers to argument N and has format specifiers. +template struct spec_field { + using char_type = Char; + mutable formatter fmt; + + template + OutputIt format(OutputIt out, const Args&... args) const { + // This ensures that the argument type is convertile to `const T&`. + const T& arg = get(args...); + basic_format_context ctx(out, {}); + return fmt.format(arg, ctx); + } +}; + +template +struct is_compiled_format> : std::true_type {}; + template struct concat { L lhs; R rhs; @@ -450,7 +473,8 @@ constexpr auto compile_format_string(S format_str); template constexpr auto parse_tail(T head, S format_str) { - if constexpr (POS != to_string_view(format_str).size()) { + if constexpr (POS != + basic_string_view(format_str).size()) { constexpr auto tail = compile_format_string(format_str); if constexpr (std::is_same, unknown_format>()) @@ -462,6 +486,21 @@ constexpr auto parse_tail(T head, S format_str) { } } +template struct parse_specs_result { + formatter fmt; + size_t end; +}; + +template +constexpr parse_specs_result parse_specs(basic_string_view str, + size_t pos) { + str.remove_prefix(pos); + auto ctx = basic_format_parse_context(str); + auto f = formatter(); + auto end = f.parse(ctx); + return {f, pos + (end - str.data()) + 1}; +} + // Compiles a non-empty format string and returns the compiled representation // or unknown_format() on unrecognized input. template @@ -475,12 +514,13 @@ constexpr auto compile_format_string(S format_str) { return parse_tail(make_text(str, POS, 1), format_str); } else if constexpr (str[POS + 1] == '}') { using type = get_type; - if constexpr (std::is_same::value) { - return parse_tail(field(), - format_str); - } else { - return unknown_format(); - } + return parse_tail(field(), + format_str); + } else if constexpr (str[POS + 1] == ':') { + using type = get_type; + constexpr auto result = parse_specs(str, POS + 2); + return parse_tail( + spec_field{result.fmt}, format_str); } else { return unknown_format(); } @@ -494,100 +534,130 @@ constexpr auto compile_format_string(S format_str) { format_str); } } -#endif // __cpp_if_constexpr -} // namespace internal -#if FMT_USE_CONSTEXPR -# ifdef __cpp_if_constexpr template ::value)> + FMT_ENABLE_IF(is_compile_string::value || + detail::is_compiled_string::value)> constexpr auto compile(S format_str) { constexpr basic_string_view str = format_str; if constexpr (str.size() == 0) { - return internal::make_text(str, 0, 0); + return detail::make_text(str, 0, 0); } else { constexpr auto result = - internal::compile_format_string, 0, 0>( + detail::compile_format_string, 0, 0>( format_str); if constexpr (std::is_same, - internal::unknown_format>()) { - return internal::compiled_format(to_string_view(format_str)); + detail::unknown_format>()) { + return detail::compiled_format(to_string_view(format_str)); } else { return result; } } } +#else +template ::value)> +constexpr auto compile(S format_str) -> detail::compiled_format { + return detail::compiled_format(to_string_view(format_str)); +} +#endif // __cpp_if_constexpr + +// Compiles the format string which must be a string literal. +template +auto compile(const Char (&format_str)[N]) + -> detail::compiled_format { + return detail::compiled_format( + basic_string_view(format_str, N - 1)); +} +} // namespace detail + +// DEPRECATED! use FMT_COMPILE instead. +template +FMT_DEPRECATED auto compile(const Args&... args) + -> decltype(detail::compile(args...)) { + return detail::compile(args...); +} + +#if FMT_USE_CONSTEXPR +# ifdef __cpp_if_constexpr template ::value)> -std::basic_string format(const CompiledFormat& cf, const Args&... args) { + FMT_ENABLE_IF(detail::is_compiled_format::value)> +FMT_INLINE std::basic_string format(const CompiledFormat& cf, + const Args&... args) { basic_memory_buffer buffer; - cf.format(std::back_inserter(buffer), args...); + detail::buffer& base = buffer; + cf.format(std::back_inserter(base), args...); return to_string(buffer); } template ::value)> + FMT_ENABLE_IF(detail::is_compiled_format::value)> OutputIt format_to(OutputIt out, const CompiledFormat& cf, const Args&... args) { return cf.format(out, args...); } -# else -template ::value)> -constexpr auto compile(S format_str) -> internal::compiled_format { - return internal::compiled_format(to_string_view(format_str)); -} # endif // __cpp_if_constexpr #endif // FMT_USE_CONSTEXPR -// Compiles the format string which must be a string literal. -template -auto compile(const Char (&format_str)[N]) - -> internal::compiled_format { - return internal::compiled_format( - basic_string_view(format_str, N - 1)); -} - template ::value)> std::basic_string format(const CompiledFormat& cf, const Args&... args) { basic_memory_buffer buffer; - using range = buffer_range; using context = buffer_context; - internal::cf::vformat_to(range(buffer), cf, - make_format_args(args...)); + detail::buffer& base = buffer; + detail::cf::vformat_to(std::back_inserter(base), cf, + make_format_args(args...)); return to_string(buffer); } +template ::value)> +FMT_INLINE std::basic_string format(const S&, + Args&&... args) { + constexpr basic_string_view str = S(); + if (str.size() == 2 && str[0] == '{' && str[1] == '}') + return fmt::to_string(detail::first(args...)); + constexpr auto compiled = detail::compile(S()); + return format(compiled, std::forward(args)...); +} + template ::value)> OutputIt format_to(OutputIt out, const CompiledFormat& cf, const Args&... args) { using char_type = typename CompiledFormat::char_type; - using range = internal::output_range; using context = format_context_t; - return internal::cf::vformat_to(range(out), cf, - make_format_args(args...)); + return detail::cf::vformat_to(out, cf, + make_format_args(args...)); } -template ::value)> +template ::value)> +OutputIt format_to(OutputIt out, const S&, const Args&... args) { + constexpr auto compiled = detail::compile(S()); + return format_to(out, compiled, args...); +} + +template < + typename OutputIt, typename CompiledFormat, typename... Args, + FMT_ENABLE_IF(detail::is_output_iterator::value&& std::is_base_of< + detail::basic_compiled_format, CompiledFormat>::value)> format_to_n_result format_to_n(OutputIt out, size_t n, const CompiledFormat& cf, const Args&... args) { auto it = - format_to(internal::truncating_iterator(out, n), cf, args...); + format_to(detail::truncating_iterator(out, n), cf, args...); return {it.base(), it.count()}; } template -std::size_t formatted_size(const CompiledFormat& cf, const Args&... args) { - return format_to(internal::counting_iterator(), cf, args...).count(); +size_t formatted_size(const CompiledFormat& cf, const Args&... args) { + return format_to(detail::counting_iterator(), cf, args...).count(); } FMT_END_NAMESPACE diff --git a/oss/fmt/include/fmt/core.h b/oss/fmt/include/fmt/core.h index 6df2875acfc..761162eb248 100644 --- a/oss/fmt/include/fmt/core.h +++ b/oss/fmt/include/fmt/core.h @@ -18,32 +18,7 @@ #include // The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 60200 - -#ifdef __has_feature -# define FMT_HAS_FEATURE(x) __has_feature(x) -#else -# define FMT_HAS_FEATURE(x) 0 -#endif - -#if defined(__has_include) && !defined(__INTELLISENSE__) && \ - !(defined(__INTEL_COMPILER) && __INTEL_COMPILER < 1600) -# define FMT_HAS_INCLUDE(x) __has_include(x) -#else -# define FMT_HAS_INCLUDE(x) 0 -#endif - -#ifdef __has_cpp_attribute -# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) -#else -# define FMT_HAS_CPP_ATTRIBUTE(x) 0 -#endif - -#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \ - (__cplusplus >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) - -#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ - (__cplusplus >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) +#define FMT_VERSION 70001 #ifdef __clang__ # define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) @@ -57,6 +32,12 @@ # define FMT_GCC_VERSION 0 #endif +#if defined(__INTEL_COMPILER) +# define FMT_ICC_VERSION __INTEL_COMPILER +#else +# define FMT_ICC_VERSION 0 +#endif + #if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) # define FMT_HAS_GXX_CXX11 FMT_GCC_VERSION #else @@ -71,17 +52,43 @@ #ifdef _MSC_VER # define FMT_MSC_VER _MSC_VER +# define FMT_SUPPRESS_MSC_WARNING(n) __pragma(warning(suppress : n)) #else # define FMT_MSC_VER 0 +# define FMT_SUPPRESS_MSC_WARNING(n) +#endif +#ifdef __has_feature +# define FMT_HAS_FEATURE(x) __has_feature(x) +#else +# define FMT_HAS_FEATURE(x) 0 #endif +#if defined(__has_include) && !defined(__INTELLISENSE__) && \ + !(FMT_ICC_VERSION && FMT_ICC_VERSION < 1600) +# define FMT_HAS_INCLUDE(x) __has_include(x) +#else +# define FMT_HAS_INCLUDE(x) 0 +#endif + +#ifdef __has_cpp_attribute +# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +# define FMT_HAS_CPP_ATTRIBUTE(x) 0 +#endif + +#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \ + (__cplusplus >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + +#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ + (__cplusplus >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + // Check if relaxed C++14 constexpr is supported. // GCC doesn't allow throw in constexpr until version 6 (bug 67371). #ifndef FMT_USE_CONSTEXPR # define FMT_USE_CONSTEXPR \ (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1910 || \ (FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L)) && \ - !FMT_NVCC + !FMT_NVCC && !FMT_ICC_VERSION #endif #if FMT_USE_CONSTEXPR # define FMT_CONSTEXPR constexpr @@ -141,14 +148,6 @@ # define FMT_NORETURN #endif -#ifndef FMT_MAYBE_UNUSED -# if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused) -# define FMT_MAYBE_UNUSED [[maybe_unused]] -# else -# define FMT_MAYBE_UNUSED -# endif -#endif - #ifndef FMT_DEPRECATED # if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900 # define FMT_DEPRECATED [[deprecated]] @@ -164,12 +163,20 @@ #endif // Workaround broken [[deprecated]] in the Intel, PGI and NVCC compilers. -#if defined(__INTEL_COMPILER) || defined(__PGI) || FMT_NVCC +#if FMT_ICC_VERSION || defined(__PGI) || FMT_NVCC # define FMT_DEPRECATED_ALIAS #else # define FMT_DEPRECATED_ALIAS FMT_DEPRECATED #endif +#ifndef FMT_INLINE +# if FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_INLINE inline __attribute__((always_inline)) +# else +# define FMT_INLINE inline +# endif +#endif + #ifndef FMT_BEGIN_NAMESPACE # if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \ FMT_MSC_VER >= 1900 @@ -181,39 +188,29 @@ # define FMT_INLINE_NAMESPACE namespace # define FMT_END_NAMESPACE \ } \ - using namespace v6; \ + using namespace v7; \ } # endif # define FMT_BEGIN_NAMESPACE \ namespace fmt { \ - FMT_INLINE_NAMESPACE v6 { + FMT_INLINE_NAMESPACE v7 { #endif #if !defined(FMT_HEADER_ONLY) && defined(_WIN32) -# if FMT_MSC_VER -# define FMT_NO_W4275 __pragma(warning(suppress : 4275)) -# else -# define FMT_NO_W4275 -# endif -# define FMT_CLASS_API FMT_NO_W4275 +# define FMT_CLASS_API FMT_SUPPRESS_MSC_WARNING(4275) # ifdef FMT_EXPORT # define FMT_API __declspec(dllexport) +# define FMT_EXTERN_TEMPLATE_API FMT_API +# define FMT_EXPORTED # elif defined(FMT_SHARED) # define FMT_API __declspec(dllimport) # define FMT_EXTERN_TEMPLATE_API FMT_API # endif -#endif -#ifndef FMT_CLASS_API +#else # define FMT_CLASS_API #endif #ifndef FMT_API -# if FMT_GCC_VERSION || FMT_CLANG_VERSION -# define FMT_API __attribute__((visibility("default"))) -# define FMT_EXTERN_TEMPLATE_API FMT_API -# define FMT_INSTANTIATION_DEF_API -# else -# define FMT_API -# endif +# define FMT_API #endif #ifndef FMT_EXTERN_TEMPLATE_API # define FMT_EXTERN_TEMPLATE_API @@ -270,14 +267,11 @@ struct monostate {}; // to workaround a bug in MSVC 2019 (see #1140 and #1186). #define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0 -namespace internal { +namespace detail { // A helper function to suppress bogus "conditional expression is constant" // warnings. -template FMT_CONSTEXPR T const_check(T value) { return value; } - -// A workaround for gcc 4.8 to make void_t work in a SFINAE context. -template struct void_t_impl { using type = void; }; +template constexpr T const_check(T value) { return value; } FMT_NORETURN FMT_API void assert_fail(const char* file, int line, const char* message); @@ -290,7 +284,7 @@ FMT_NORETURN FMT_API void assert_fail(const char* file, int line, # define FMT_ASSERT(condition, message) \ ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ ? (void)0 \ - : ::fmt::internal::assert_fail(__FILE__, __LINE__, (message))) + : ::fmt::detail::assert_fail(__FILE__, __LINE__, (message))) # endif #endif @@ -324,7 +318,7 @@ FMT_CONSTEXPR typename std::make_unsigned::type to_unsigned(Int value) { return static_cast::type>(value); } -constexpr unsigned char micro[] = "\u00B5"; +FMT_SUPPRESS_MSC_WARNING(4566) constexpr unsigned char micro[] = "\u00B5"; template constexpr bool is_unicode() { return FMT_UNICODE || sizeof(Char) != 1 || @@ -336,10 +330,11 @@ using char8_type = char8_t; #else enum char8_type : unsigned char {}; #endif -} // namespace internal +} // namespace detail -template -using void_t = typename internal::void_t_impl::type; +#ifdef FMT_USE_INTERNAL +namespace internal = detail; // DEPRECATED +#endif /** An implementation of ``std::basic_string_view`` for pre-C++17. It provides a @@ -354,14 +349,13 @@ template class basic_string_view { size_t size_; public: - using char_type FMT_DEPRECATED_ALIAS = Char; using value_type = Char; using iterator = const Char*; - FMT_CONSTEXPR basic_string_view() FMT_NOEXCEPT : data_(nullptr), size_(0) {} + constexpr basic_string_view() FMT_NOEXCEPT : data_(nullptr), size_(0) {} /** Constructs a string reference object from a C string and a size. */ - FMT_CONSTEXPR basic_string_view(const Char* s, size_t count) FMT_NOEXCEPT + constexpr basic_string_view(const Char* s, size_t count) FMT_NOEXCEPT : data_(s), size_(count) {} @@ -384,22 +378,21 @@ template class basic_string_view { : data_(s.data()), size_(s.size()) {} - template < - typename S, - FMT_ENABLE_IF(std::is_same>::value)> + template >::value)> FMT_CONSTEXPR basic_string_view(S s) FMT_NOEXCEPT : data_(s.data()), size_(s.size()) {} /** Returns a pointer to the string data. */ - FMT_CONSTEXPR const Char* data() const { return data_; } + constexpr const Char* data() const { return data_; } /** Returns the string size. */ - FMT_CONSTEXPR size_t size() const { return size_; } + constexpr size_t size() const { return size_; } - FMT_CONSTEXPR iterator begin() const { return data_; } - FMT_CONSTEXPR iterator end() const { return data_ + size_; } + constexpr iterator begin() const { return data_; } + constexpr iterator end() const { return data_ + size_; } - FMT_CONSTEXPR const Char& operator[](size_t pos) const { return data_[pos]; } + constexpr const Char& operator[](size_t pos) const { return data_[pos]; } FMT_CONSTEXPR void remove_prefix(size_t n) { data_ += n; @@ -438,16 +431,11 @@ template class basic_string_view { using string_view = basic_string_view; using wstring_view = basic_string_view; -#ifndef __cpp_char8_t -// char8_t is deprecated; use char instead. -using char8_t FMT_DEPRECATED_ALIAS = internal::char8_type; -#endif - /** Specifies if ``T`` is a character type. Can be specialized by users. */ template struct is_char : std::false_type {}; template <> struct is_char : std::true_type {}; template <> struct is_char : std::true_type {}; -template <> struct is_char : std::true_type {}; +template <> struct is_char : std::true_type {}; template <> struct is_char : std::true_type {}; template <> struct is_char : std::true_type {}; @@ -484,14 +472,13 @@ inline basic_string_view to_string_view(basic_string_view s) { } template >::value)> -inline basic_string_view to_string_view( - internal::std_string_view s) { + FMT_ENABLE_IF(!std::is_empty>::value)> +inline basic_string_view to_string_view(detail::std_string_view s) { return s; } // A base class for compile-time strings. It is defined in the fmt namespace to -// make formatting functions visible via ADL, e.g. format(fmt("{}"), 42). +// make formatting functions visible via ADL, e.g. format(FMT_STRING("{}"), 42). struct compile_string {}; template @@ -502,9 +489,9 @@ constexpr basic_string_view to_string_view(const S& s) { return s; } -namespace internal { +namespace detail { void to_string_view(...); -using fmt::v6::to_string_view; +using fmt::v7::to_string_view; // Specifies whether S is a string type convertible to fmt::basic_string_view. // It should be a constexpr function but MSVC 2017 fails to compile it in @@ -520,16 +507,16 @@ template struct char_t_impl::value>> { }; struct error_handler { - FMT_CONSTEXPR error_handler() = default; - FMT_CONSTEXPR error_handler(const error_handler&) = default; + constexpr error_handler() = default; + constexpr error_handler(const error_handler&) = default; // This function is intentionally not constexpr to give a compile-time error. FMT_NORETURN FMT_API void on_error(const char* message); }; -} // namespace internal +} // namespace detail /** String's character type. */ -template using char_t = typename internal::char_t_impl::type; +template using char_t = typename detail::char_t_impl::type; /** \rst @@ -547,7 +534,7 @@ template using char_t = typename internal::char_t_impl::type; +-----------------------+-------------------------------------+ \endrst */ -template +template class basic_format_parse_context : private ErrorHandler { private: basic_string_view format_str_; @@ -557,26 +544,24 @@ class basic_format_parse_context : private ErrorHandler { using char_type = Char; using iterator = typename basic_string_view::iterator; - explicit FMT_CONSTEXPR basic_format_parse_context( - basic_string_view format_str, ErrorHandler eh = ErrorHandler()) + explicit constexpr basic_format_parse_context( + basic_string_view format_str, ErrorHandler eh = {}) : ErrorHandler(eh), format_str_(format_str), next_arg_id_(0) {} /** Returns an iterator to the beginning of the format string range being parsed. */ - FMT_CONSTEXPR iterator begin() const FMT_NOEXCEPT { - return format_str_.begin(); - } + constexpr iterator begin() const FMT_NOEXCEPT { return format_str_.begin(); } /** Returns an iterator past the end of the format string range being parsed. */ - FMT_CONSTEXPR iterator end() const FMT_NOEXCEPT { return format_str_.end(); } + constexpr iterator end() const FMT_NOEXCEPT { return format_str_.end(); } /** Advances the begin iterator to ``it``. */ FMT_CONSTEXPR void advance_to(iterator it) { - format_str_.remove_prefix(internal::to_unsigned(it - begin())); + format_str_.remove_prefix(detail::to_unsigned(it - begin())); } /** @@ -584,6 +569,8 @@ class basic_format_parse_context : private ErrorHandler { the next argument index and switches to the automatic indexing. */ FMT_CONSTEXPR int next_arg_id() { + // Don't check if the argument id is valid to avoid overhead and because it + // will be checked during formatting anyway. if (next_arg_id_ >= 0) return next_arg_id_++; on_error("cannot switch from manual to automatic argument indexing"); return 0; @@ -606,20 +593,15 @@ class basic_format_parse_context : private ErrorHandler { ErrorHandler::on_error(message); } - FMT_CONSTEXPR ErrorHandler error_handler() const { return *this; } + constexpr ErrorHandler error_handler() const { return *this; } }; using format_parse_context = basic_format_parse_context; using wformat_parse_context = basic_format_parse_context; -template -using basic_parse_context FMT_DEPRECATED_ALIAS = - basic_format_parse_context; -using parse_context FMT_DEPRECATED_ALIAS = basic_format_parse_context; -using wparse_context FMT_DEPRECATED_ALIAS = basic_format_parse_context; - template class basic_format_arg; template class basic_format_args; +template class dynamic_format_arg_store; // A formatter for objects of type T. template @@ -628,43 +610,44 @@ struct formatter { formatter() = delete; }; -template -struct FMT_DEPRECATED convert_to_int - : bool_constant::value && - std::is_convertible::value> {}; - // Specifies if T has an enabled formatter specialization. A type can be // formattable even if it doesn't have a formatter e.g. via a conversion. template using has_formatter = std::is_constructible>; -namespace internal { +namespace detail { -/** A contiguous memory buffer with an optional growing ability. */ +/** + \rst + A contiguous memory buffer with an optional growing ability. It is an internal + class and shouldn't be used directly, only via `~fmt::basic_memory_buffer`. + \endrst + */ template class buffer { private: T* ptr_; - std::size_t size_; - std::size_t capacity_; + size_t size_; + size_t capacity_; protected: // Don't initialize ptr_ since it is not accessed to save a few cycles. - buffer(std::size_t sz) FMT_NOEXCEPT : size_(sz), capacity_(sz) {} + FMT_SUPPRESS_MSC_WARNING(26495) + buffer(size_t sz) FMT_NOEXCEPT : size_(sz), capacity_(sz) {} - buffer(T* p = nullptr, std::size_t sz = 0, std::size_t cap = 0) FMT_NOEXCEPT + buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0) FMT_NOEXCEPT : ptr_(p), size_(sz), capacity_(cap) {} /** Sets the buffer data and capacity. */ - void set(T* buf_data, std::size_t buf_capacity) FMT_NOEXCEPT { + void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT { ptr_ = buf_data; capacity_ = buf_capacity; } /** Increases the buffer capacity to hold at least *capacity* elements. */ - virtual void grow(std::size_t capacity) = 0; + virtual void grow(size_t capacity) = 0; public: using value_type = T; @@ -681,10 +664,10 @@ template class buffer { const T* end() const FMT_NOEXCEPT { return ptr_ + size_; } /** Returns the size of this buffer. */ - std::size_t size() const FMT_NOEXCEPT { return size_; } + size_t size() const FMT_NOEXCEPT { return size_; } /** Returns the capacity of this buffer. */ - std::size_t capacity() const FMT_NOEXCEPT { return capacity_; } + size_t capacity() const FMT_NOEXCEPT { return capacity_; } /** Returns a pointer to the buffer data. */ T* data() FMT_NOEXCEPT { return ptr_; } @@ -695,7 +678,7 @@ template class buffer { /** Resizes the buffer. If T is a POD type new elements may not be initialized. */ - void resize(std::size_t new_size) { + void resize(size_t new_size) { reserve(new_size); size_ = new_size; } @@ -704,7 +687,7 @@ template class buffer { void clear() { size_ = 0; } /** Reserves space to store at least *capacity* elements. */ - void reserve(std::size_t new_capacity) { + void reserve(size_t new_capacity) { if (new_capacity > capacity_) grow(new_capacity); } @@ -729,7 +712,7 @@ class container_buffer : public buffer { Container& container_; protected: - void grow(std::size_t capacity) FMT_OVERRIDE { + void grow(size_t capacity) FMT_OVERRIDE { container_.resize(capacity); this->set(&container_[0], capacity); } @@ -760,12 +743,78 @@ template using has_fallback_formatter = std::is_constructible>; -template struct named_arg_base; -template struct named_arg; +struct view {}; + +template struct named_arg : view { + const Char* name; + const T& value; + named_arg(const Char* n, const T& v) : name(n), value(v) {} +}; + +template struct named_arg_info { + const Char* name; + int id; +}; + +template +struct arg_data { + // args_[0].named_args points to named_args_ to avoid bloating format_args. + T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : 1)]; + named_arg_info named_args_[NUM_NAMED_ARGS]; + + template + arg_data(const U&... init) : args_{T(named_args_, NUM_NAMED_ARGS), init...} {} + arg_data(const arg_data& other) = delete; + const T* args() const { return args_ + 1; } + named_arg_info* named_args() { return named_args_; } +}; + +template +struct arg_data { + T args_[NUM_ARGS != 0 ? NUM_ARGS : 1]; + + template + FMT_INLINE arg_data(const U&... init) : args_{init...} {} + FMT_INLINE const T* args() const { return args_; } + FMT_INLINE std::nullptr_t named_args() { return nullptr; } +}; + +template +inline void init_named_args(named_arg_info*, int, int) {} + +template +void init_named_args(named_arg_info* named_args, int arg_count, + int named_arg_count, const T&, const Tail&... args) { + init_named_args(named_args, arg_count + 1, named_arg_count, args...); +} + +template +void init_named_args(named_arg_info* named_args, int arg_count, + int named_arg_count, const named_arg& arg, + const Tail&... args) { + named_args[named_arg_count++] = {arg.name, arg_count}; + init_named_args(named_args, arg_count + 1, named_arg_count, args...); +} + +template +FMT_INLINE void init_named_args(std::nullptr_t, int, int, const Args&...) {} + +template struct is_named_arg : std::false_type {}; + +template +struct is_named_arg> : std::true_type {}; + +template constexpr size_t count() { return B ? 1 : 0; } +template constexpr size_t count() { + return (B1 ? 1 : 0) + count(); +} + +template constexpr size_t count_named_args() { + return count::value...>(); +} enum class type { none_type, - named_arg_type, // Integer types should go first, int_type, uint_type, @@ -796,7 +845,6 @@ struct type_constant : std::integral_constant {}; struct type_constant \ : std::integral_constant {} -FMT_TYPE_CONSTANT(const named_arg_base&, named_arg_type); FMT_TYPE_CONSTANT(int, int_type); FMT_TYPE_CONSTANT(unsigned, uint_type); FMT_TYPE_CONSTANT(long long, long_long_type); @@ -812,23 +860,26 @@ FMT_TYPE_CONSTANT(const Char*, cstring_type); FMT_TYPE_CONSTANT(basic_string_view, string_type); FMT_TYPE_CONSTANT(const void*, pointer_type); -FMT_CONSTEXPR bool is_integral_type(type t) { - FMT_ASSERT(t != type::named_arg_type, "invalid argument type"); +constexpr bool is_integral_type(type t) { return t > type::none_type && t <= type::last_integer_type; } -FMT_CONSTEXPR bool is_arithmetic_type(type t) { - FMT_ASSERT(t != type::named_arg_type, "invalid argument type"); +constexpr bool is_arithmetic_type(type t) { return t > type::none_type && t <= type::last_numeric_type; } template struct string_value { const Char* data; - std::size_t size; + size_t size; +}; + +template struct named_arg_value { + const named_arg_info* data; + size_t size; }; template struct custom_value { - using parse_context = basic_format_parse_context; + using parse_context = typename Context::parse_context_type; const void* value; void (*format)(const void* arg, parse_context& parse_ctx, Context& ctx); }; @@ -853,28 +904,30 @@ template class value { const void* pointer; string_value string; custom_value custom; - const named_arg_base* named_arg; + named_arg_value named_args; }; - FMT_CONSTEXPR value(int val = 0) : int_value(val) {} - FMT_CONSTEXPR value(unsigned val) : uint_value(val) {} - value(long long val) : long_long_value(val) {} - value(unsigned long long val) : ulong_long_value(val) {} - value(int128_t val) : int128_value(val) {} - value(uint128_t val) : uint128_value(val) {} - value(float val) : float_value(val) {} - value(double val) : double_value(val) {} - value(long double val) : long_double_value(val) {} - value(bool val) : bool_value(val) {} - value(char_type val) : char_value(val) {} - value(const char_type* val) { string.data = val; } - value(basic_string_view val) { + constexpr FMT_INLINE value(int val = 0) : int_value(val) {} + constexpr FMT_INLINE value(unsigned val) : uint_value(val) {} + FMT_INLINE value(long long val) : long_long_value(val) {} + FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {} + FMT_INLINE value(int128_t val) : int128_value(val) {} + FMT_INLINE value(uint128_t val) : uint128_value(val) {} + FMT_INLINE value(float val) : float_value(val) {} + FMT_INLINE value(double val) : double_value(val) {} + FMT_INLINE value(long double val) : long_double_value(val) {} + FMT_INLINE value(bool val) : bool_value(val) {} + FMT_INLINE value(char_type val) : char_value(val) {} + FMT_INLINE value(const char_type* val) { string.data = val; } + FMT_INLINE value(basic_string_view val) { string.data = val.data(); string.size = val.size(); } - value(const void* val) : pointer(val) {} + FMT_INLINE value(const void* val) : pointer(val) {} + FMT_INLINE value(const named_arg_info* args, size_t size) + : named_args{args, size} {} - template value(const T& val) { + template FMT_INLINE value(const T& val) { custom.value = &val; // Get the formatter type through the context to allow different contexts // have different extension points, e.g. `formatter` for `format` and @@ -885,14 +938,12 @@ template class value { fallback_formatter>>; } - value(const named_arg_base& val) { named_arg = &val; } - private: // Formats an argument of a custom type, such as a user-defined class. template - static void format_custom_arg( - const void* arg, basic_format_parse_context& parse_ctx, - Context& ctx) { + static void format_custom_arg(const void* arg, + typename Context::parse_context_type& parse_ctx, + Context& ctx) { Formatter f; parse_ctx.advance_to(f.parse(parse_ctx)); ctx.advance_to(f.format(*static_cast(arg), ctx)); @@ -972,6 +1023,14 @@ template struct arg_mapper { static_assert(std::is_same::value, "invalid string type"); return reinterpret_cast(val); } + FMT_CONSTEXPR const char* map(signed char* val) { + const auto* const_val = val; + return map(const_val); + } + FMT_CONSTEXPR const char* map(unsigned char* val) { + const auto* const_val = val; + return map(const_val); + } FMT_CONSTEXPR const void* map(void* val) { return val; } FMT_CONSTEXPR const void* map(const void* val) { return val; } @@ -1003,11 +1062,9 @@ template struct arg_mapper { } template - FMT_CONSTEXPR const named_arg_base& map( - const named_arg& val) { - auto arg = make_arg(val.value); - std::memcpy(val.data, &arg, sizeof(arg)); - return val; + FMT_CONSTEXPR auto map(const named_arg& val) + -> decltype(std::declval().map(val.value)) { + return map(val.value); } int map(...) { @@ -1027,23 +1084,22 @@ using mapped_type_constant = type_constant().map(std::declval())), typename Context::char_type>; -enum { packed_arg_bits = 5 }; +enum { packed_arg_bits = 4 }; // Maximum number of arguments with packed types. -enum { max_packed_args = 63 / packed_arg_bits }; +enum { max_packed_args = 62 / packed_arg_bits }; enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; - -template class arg_map; -} // namespace internal +enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; +} // namespace detail // A formatting argument. It is a trivially copyable/constructible type to // allow storage in basic_memory_buffer. template class basic_format_arg { private: - internal::value value_; - internal::type type_; + detail::value value_; + detail::type type_; template - friend FMT_CONSTEXPR basic_format_arg internal::make_arg( + friend FMT_CONSTEXPR basic_format_arg detail::make_arg( const T& value); template @@ -1052,34 +1108,40 @@ template class basic_format_arg { -> decltype(vis(0)); friend class basic_format_args; - friend class internal::arg_map; + friend class dynamic_format_arg_store; using char_type = typename Context::char_type; + template + friend struct detail::arg_data; + + basic_format_arg(const detail::named_arg_info* args, size_t size) + : value_(args, size) {} + public: class handle { public: - explicit handle(internal::custom_value custom) : custom_(custom) {} + explicit handle(detail::custom_value custom) : custom_(custom) {} - void format(basic_format_parse_context& parse_ctx, + void format(typename Context::parse_context_type& parse_ctx, Context& ctx) const { custom_.format(custom_.value, parse_ctx, ctx); } private: - internal::custom_value custom_; + detail::custom_value custom_; }; - FMT_CONSTEXPR basic_format_arg() : type_(internal::type::none_type) {} + constexpr basic_format_arg() : type_(detail::type::none_type) {} - FMT_CONSTEXPR explicit operator bool() const FMT_NOEXCEPT { - return type_ != internal::type::none_type; + constexpr explicit operator bool() const FMT_NOEXCEPT { + return type_ != detail::type::none_type; } - internal::type type() const { return type_; } + detail::type type() const { return type_; } - bool is_integral() const { return internal::is_integral_type(type_); } - bool is_arithmetic() const { return internal::is_arithmetic_type(type_); } + bool is_integral() const { return detail::is_integral_type(type_); } + bool is_arithmetic() const { return detail::is_arithmetic_type(type_); } }; /** @@ -1090,92 +1152,73 @@ template class basic_format_arg { \endrst */ template -FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis, - const basic_format_arg& arg) - -> decltype(vis(0)) { +FMT_CONSTEXPR_DECL FMT_INLINE auto visit_format_arg( + Visitor&& vis, const basic_format_arg& arg) -> decltype(vis(0)) { using char_type = typename Context::char_type; switch (arg.type_) { - case internal::type::none_type: + case detail::type::none_type: break; - case internal::type::named_arg_type: - FMT_ASSERT(false, "invalid argument type"); - break; - case internal::type::int_type: + case detail::type::int_type: return vis(arg.value_.int_value); - case internal::type::uint_type: + case detail::type::uint_type: return vis(arg.value_.uint_value); - case internal::type::long_long_type: + case detail::type::long_long_type: return vis(arg.value_.long_long_value); - case internal::type::ulong_long_type: + case detail::type::ulong_long_type: return vis(arg.value_.ulong_long_value); #if FMT_USE_INT128 - case internal::type::int128_type: + case detail::type::int128_type: return vis(arg.value_.int128_value); - case internal::type::uint128_type: + case detail::type::uint128_type: return vis(arg.value_.uint128_value); #else - case internal::type::int128_type: - case internal::type::uint128_type: + case detail::type::int128_type: + case detail::type::uint128_type: break; #endif - case internal::type::bool_type: + case detail::type::bool_type: return vis(arg.value_.bool_value); - case internal::type::char_type: + case detail::type::char_type: return vis(arg.value_.char_value); - case internal::type::float_type: + case detail::type::float_type: return vis(arg.value_.float_value); - case internal::type::double_type: + case detail::type::double_type: return vis(arg.value_.double_value); - case internal::type::long_double_type: + case detail::type::long_double_type: return vis(arg.value_.long_double_value); - case internal::type::cstring_type: + case detail::type::cstring_type: return vis(arg.value_.string.data); - case internal::type::string_type: + case detail::type::string_type: return vis(basic_string_view(arg.value_.string.data, arg.value_.string.size)); - case internal::type::pointer_type: + case detail::type::pointer_type: return vis(arg.value_.pointer); - case internal::type::custom_type: + case detail::type::custom_type: return vis(typename basic_format_arg::handle(arg.value_.custom)); } return vis(monostate()); } -namespace internal { -// A map from argument names to their values for named arguments. -template class arg_map { - private: - using char_type = typename Context::char_type; - - struct entry { - basic_string_view name; - basic_format_arg arg; - }; +// Checks whether T is a container with contiguous storage. +template struct is_contiguous : std::false_type {}; +template +struct is_contiguous> : std::true_type {}; +template +struct is_contiguous> : std::true_type {}; - entry* map_; - unsigned size_; +namespace detail { - void push_back(value val) { - const auto& named = *val.named_arg; - map_[size_] = {named.name, named.template deserialize()}; - ++size_; - } +template +struct is_back_insert_iterator : std::false_type {}; +template +struct is_back_insert_iterator> + : std::true_type {}; - public: - arg_map(const arg_map&) = delete; - void operator=(const arg_map&) = delete; - arg_map() : map_(nullptr), size_(0) {} - void init(const basic_format_args& args); - ~arg_map() { delete[] map_; } - - basic_format_arg find(basic_string_view name) const { - // The list is unsorted, so just return the first matching name. - for (entry *it = map_, *end = map_ + size_; it != end; ++it) { - if (it->name == name) return it->arg; - } - return {}; - } -}; +template +struct is_contiguous_back_insert_iterator : std::false_type {}; +template +struct is_contiguous_back_insert_iterator> + : is_contiguous {}; // A type-erased reference to an std::locale to avoid heavy include. class locale_ref { @@ -1207,23 +1250,30 @@ FMT_CONSTEXPR basic_format_arg make_arg(const T& value) { return arg; } -template inline value make_arg(const T& val) { return arg_mapper().map(val); } -template inline basic_format_arg make_arg(const T& value) { return make_arg(value); } template struct is_reference_wrapper : std::false_type {}; - template struct is_reference_wrapper> : std::true_type {}; +template const T& unwrap(const T& v) { return v; } +template const T& unwrap(const std::reference_wrapper& v) { + return static_cast(v); +} + class dynamic_arg_list { // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for // templates it doesn't complain about inability to deduce single translation @@ -1248,14 +1298,14 @@ class dynamic_arg_list { public: template const T& push(const Arg& arg) { - auto node = std::unique_ptr>(new typed_node(arg)); - auto& value = node->value; - node->next = std::move(head_); - head_ = std::move(node); + auto new_node = std::unique_ptr>(new typed_node(arg)); + auto& value = new_node->value; + new_node->next = std::move(head_); + head_ = std::move(new_node); return value; } }; -} // namespace internal +} // namespace detail // Formatting context. template class basic_format_context { @@ -1266,12 +1316,12 @@ template class basic_format_context { private: OutputIt out_; basic_format_args args_; - internal::arg_map map_; - internal::locale_ref loc_; + detail::locale_ref loc_; public: using iterator = OutputIt; using format_arg = basic_format_arg; + using parse_context_type = basic_format_parse_context; template using formatter_type = formatter; basic_format_context(const basic_format_context&) = delete; @@ -1282,34 +1332,38 @@ template class basic_format_context { */ basic_format_context(OutputIt out, basic_format_args ctx_args, - internal::locale_ref loc = internal::locale_ref()) + detail::locale_ref loc = detail::locale_ref()) : out_(out), args_(ctx_args), loc_(loc) {} format_arg arg(int id) const { return args_.get(id); } + format_arg arg(basic_string_view name) { return args_.get(name); } + int arg_id(basic_string_view name) { return args_.get_id(name); } + const basic_format_args& args() const { return args_; } - // Checks if manual indexing is used and returns the argument with the - // specified name. - format_arg arg(basic_string_view name); - - internal::error_handler error_handler() { return {}; } + detail::error_handler error_handler() { return {}; } void on_error(const char* message) { error_handler().on_error(message); } // Returns an iterator to the beginning of the output range. iterator out() { return out_; } // Advances the begin iterator to ``it``. - void advance_to(iterator it) { out_ = it; } + void advance_to(iterator it) { + if (!detail::is_back_insert_iterator()) out_ = it; + } - internal::locale_ref locale() { return loc_; } + detail::locale_ref locale() { return loc_; } }; template using buffer_context = - basic_format_context>, - Char>; + basic_format_context>, Char>; using format_context = buffer_context; using wformat_context = buffer_context; +// Workaround a bug in gcc: https://stackoverflow.com/q/62767544/471164. +#define FMT_BUFFER_CONTEXT(Char) \ + basic_format_context>, Char> + /** \rst An array of references to arguments. It can be implicitly converted into @@ -1326,27 +1380,35 @@ class format_arg_store { private: static const size_t num_args = sizeof...(Args); - static const bool is_packed = num_args < internal::max_packed_args; + static const size_t num_named_args = detail::count_named_args(); + static const bool is_packed = num_args <= detail::max_packed_args; - using value_type = conditional_t, + using value_type = conditional_t, basic_format_arg>; - // If the arguments are not packed, add one more element to mark the end. - value_type data_[num_args + (num_args == 0 ? 1 : 0)]; + detail::arg_data + data_; friend class basic_format_args; - public: - static constexpr unsigned long long types = - is_packed ? internal::encode_types() - : internal::is_unpacked_bit | num_args; + static constexpr unsigned long long desc = + (is_packed ? detail::encode_types() + : detail::is_unpacked_bit | num_args) | + (num_named_args != 0 + ? static_cast(detail::has_named_args_bit) + : 0); + public: format_arg_store(const Args&... args) : #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 basic_format_args(*this), #endif - data_{internal::make_arg(args)...} { + data_{detail::make_arg< + is_packed, Context, + detail::mapped_type_constant::value>(args)...} { + detail::init_named_args(data_.named_args(), 0, 0, args...); } }; @@ -1366,8 +1428,24 @@ inline format_arg_store make_format_args( /** \rst - A dynamic version of `fmt::format_arg_store<>`. - It's equipped with a storage to potentially temporary objects which lifetime + Returns a named argument to be used in a formatting function. It should only + be used in a call to a formatting function. + + **Example**:: + + fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); + \endrst + */ +template +inline detail::named_arg arg(const Char* name, const T& arg) { + static_assert(!detail::is_named_arg(), "nested named arguments"); + return {name, arg}; +} + +/** + \rst + A dynamic version of `fmt::format_arg_store`. + It's equipped with a storage to potentially temporary objects which lifetimes could be shorter than the format arguments object. It can be implicitly converted into `~fmt::basic_format_args` for passing @@ -1385,49 +1463,73 @@ class dynamic_format_arg_store using char_type = typename Context::char_type; template struct need_copy { - static constexpr internal::type mapped_type = - internal::mapped_type_constant::value; + static constexpr detail::type mapped_type = + detail::mapped_type_constant::value; enum { - value = !(internal::is_reference_wrapper::value || + value = !(detail::is_reference_wrapper::value || std::is_same>::value || - std::is_same>::value || - (mapped_type != internal::type::cstring_type && - mapped_type != internal::type::string_type && - mapped_type != internal::type::custom_type && - mapped_type != internal::type::named_arg_type)) + std::is_same>::value || + (mapped_type != detail::type::cstring_type && + mapped_type != detail::type::string_type && + mapped_type != detail::type::custom_type)) }; }; template - using stored_type = conditional_t::value, + using stored_type = conditional_t::value, std::basic_string, T>; // Storage of basic_format_arg must be contiguous. std::vector> data_; + std::vector> named_info_; // Storage of arguments not fitting into basic_format_arg must grow // without relocation because items in data_ refer to it. - internal::dynamic_arg_list dynamic_args_; + detail::dynamic_arg_list dynamic_args_; friend class basic_format_args; unsigned long long get_types() const { - return internal::is_unpacked_bit | data_.size(); + return detail::is_unpacked_bit | data_.size() | + (named_info_.empty() + ? 0ULL + : static_cast(detail::has_named_args_bit)); + } + + const basic_format_arg* data() const { + return named_info_.empty() ? data_.data() : data_.data() + 1; } template void emplace_arg(const T& arg) { - data_.emplace_back(internal::make_arg(arg)); + data_.emplace_back(detail::make_arg(arg)); + } + + template + void emplace_arg(const detail::named_arg& arg) { + if (named_info_.empty()) { + constexpr const detail::named_arg_info* zero_ptr{nullptr}; + data_.insert(data_.begin(), {zero_ptr, 0}); + } + data_.emplace_back(detail::make_arg(detail::unwrap(arg.value))); + auto pop_one = [](std::vector>* data) { + data->pop_back(); + }; + std::unique_ptr>, decltype(pop_one)> + guard{&data_, pop_one}; + named_info_.push_back({arg.name, static_cast(data_.size() - 2u)}); + data_[0].value_.named_args = {named_info_.data(), named_info_.size()}; + guard.release(); } public: /** \rst - Adds an argument into the dynamic store for later passing to a formating + Adds an argument into the dynamic store for later passing to a formatting function. - Note that custom types and string types (but not string views!) are copied - into the store with dynamic memory (in addition to resizing vector). + Note that custom types and string types (but not string views) are copied + into the store dynamically allocating memory if necessary. **Example**:: @@ -1439,25 +1541,78 @@ class dynamic_format_arg_store \endrst */ template void push_back(const T& arg) { - static_assert( - !std::is_base_of, T>::value, - "named arguments are not supported yet"); - if (internal::const_check(need_copy::value)) + if (detail::const_check(need_copy::value)) emplace_arg(dynamic_args_.push>(arg)); else - emplace_arg(arg); + emplace_arg(detail::unwrap(arg)); } /** + \rst Adds a reference to the argument into the dynamic store for later passing to - a formating function. + a formatting function. Supports named arguments wrapped in + ``std::reference_wrapper`` via ``std::ref()``/``std::cref()``. + + **Example**:: + + fmt::dynamic_format_arg_store store; + char str[] = "1234567890"; + store.push_back(std::cref(str)); + int a1_val{42}; + auto a1 = fmt::arg("a1_", a1_val); + store.push_back(std::cref(a1)); + + // Changing str affects the output but only for string and custom types. + str[0] = 'X'; + + std::string result = fmt::vformat("{} and {a1_}"); + assert(result == "X234567890 and 42"); + \endrst */ template void push_back(std::reference_wrapper arg) { static_assert( - need_copy::value, + detail::is_named_arg::type>::value || + need_copy::value, "objects of built-in types and string views are always copied"); emplace_arg(arg.get()); } + + /** + Adds named argument into the dynamic store for later passing to a formatting + function. ``std::reference_wrapper`` is supported to avoid copying of the + argument. + */ + template + void push_back(const detail::named_arg& arg) { + const char_type* arg_name = + dynamic_args_.push>(arg.name).c_str(); + if (detail::const_check(need_copy::value)) { + emplace_arg( + fmt::arg(arg_name, dynamic_args_.push>(arg.value))); + } else { + emplace_arg(fmt::arg(arg_name, arg.value)); + } + } + + /** Erase all elements from the store */ + void clear() { + data_.clear(); + named_info_.clear(); + dynamic_args_ = detail::dynamic_arg_list(); + } + + /** + \rst + Reserves space to store at least *new_cap* arguments including + *new_cap_named* named arguments. + \endrst + */ + void reserve(size_t new_cap, size_t new_cap_named) { + FMT_ASSERT(new_cap >= new_cap_named, + "Set of arguments includes set of named arguments"); + data_.reserve(new_cap); + named_info_.reserve(new_cap_named); + } }; /** @@ -1476,49 +1631,40 @@ template class basic_format_args { using format_arg = basic_format_arg; private: - // To reduce compiled code size per formatting function call, types of first - // max_packed_args arguments are passed in the types_ field. - unsigned long long types_; + // A descriptor that contains information about formatting arguments. + // If the number of arguments is less or equal to max_packed_args then + // argument types are passed in the descriptor. This reduces binary code size + // per formatting function call. + unsigned long long desc_; union { - // If the number of arguments is less than max_packed_args, the argument - // values are stored in values_, otherwise they are stored in args_. - // This is done to reduce compiled code size as storing larger objects + // If is_packed() returns true then argument values are stored in values_; + // otherwise they are stored in args_. This is done to improve cache + // locality and reduce compiled code size since storing larger objects // may require more code (at least on x86-64) even if the same amount of // data is actually copied to stack. It saves ~10% on the bloat test. - const internal::value* values_; + const detail::value* values_; const format_arg* args_; }; - bool is_packed() const { return (types_ & internal::is_unpacked_bit) == 0; } - - internal::type type(int index) const { - int shift = index * internal::packed_arg_bits; - unsigned int mask = (1 << internal::packed_arg_bits) - 1; - return static_cast((types_ >> shift) & mask); + bool is_packed() const { return (desc_ & detail::is_unpacked_bit) == 0; } + bool has_named_args() const { + return (desc_ & detail::has_named_args_bit) != 0; } - friend class internal::arg_map; - - void set_data(const internal::value* values) { values_ = values; } - void set_data(const format_arg* args) { args_ = args; } - - format_arg do_get(int index) const { - format_arg arg; - if (!is_packed()) { - auto num_args = max_size(); - if (index < num_args) arg = args_[index]; - return arg; - } - if (index > internal::max_packed_args) return arg; - arg.type_ = type(index); - if (arg.type_ == internal::type::none_type) return arg; - internal::value& val = arg.value_; - val = values_[index]; - return arg; + detail::type type(int index) const { + int shift = index * detail::packed_arg_bits; + unsigned int mask = (1 << detail::packed_arg_bits) - 1; + return static_cast((desc_ >> shift) & mask); } + basic_format_args(unsigned long long desc, + const detail::value* values) + : desc_(desc), values_(values) {} + basic_format_args(unsigned long long desc, const format_arg* args) + : desc_(desc), args_(args) {} + public: - basic_format_args() : types_(0) {} + basic_format_args() : desc_(0) {} /** \rst @@ -1526,10 +1672,8 @@ template class basic_format_args { \endrst */ template - basic_format_args(const format_arg_store& store) - : types_(store.types) { - set_data(store.data_); - } + FMT_INLINE basic_format_args(const format_arg_store& store) + : basic_format_args(store.desc, store.data_.args()) {} /** \rst @@ -1537,10 +1681,8 @@ template class basic_format_args { `~fmt::dynamic_format_arg_store`. \endrst */ - basic_format_args(const dynamic_format_arg_store& store) - : types_(store.get_types()) { - set_data(store.data_.data()); - } + FMT_INLINE basic_format_args(const dynamic_format_arg_store& store) + : basic_format_args(store.get_types(), store.data()) {} /** \rst @@ -1548,22 +1690,42 @@ template class basic_format_args { \endrst */ basic_format_args(const format_arg* args, int count) - : types_(internal::is_unpacked_bit | internal::to_unsigned(count)) { - set_data(args); - } + : basic_format_args(detail::is_unpacked_bit | detail::to_unsigned(count), + args) {} - /** Returns the argument at specified index. */ - format_arg get(int index) const { - format_arg arg = do_get(index); - if (arg.type_ == internal::type::named_arg_type) - arg = arg.value_.named_arg->template deserialize(); + /** Returns the argument with the specified id. */ + format_arg get(int id) const { + format_arg arg; + if (!is_packed()) { + if (id < max_size()) arg = args_[id]; + return arg; + } + if (id >= detail::max_packed_args) return arg; + arg.type_ = type(id); + if (arg.type_ == detail::type::none_type) return arg; + arg.value_ = values_[id]; return arg; } + template format_arg get(basic_string_view name) const { + int id = get_id(name); + return id >= 0 ? get(id) : format_arg(); + } + + template int get_id(basic_string_view name) const { + if (!has_named_args()) return {}; + const auto& named_args = + (is_packed() ? values_[-1] : args_[-1].value_).named_args; + for (size_t i = 0; i < named_args.size; ++i) { + if (named_args.data[i].name == name) return named_args.data[i].id; + } + return -1; + } + int max_size() const { - unsigned long long max_packed = internal::max_packed_args; + unsigned long long max_packed = detail::max_packed_args; return static_cast(is_packed() ? max_packed - : types_ & ~internal::is_unpacked_bit); + : desc_ & ~detail::is_unpacked_bit); } }; @@ -1571,93 +1733,48 @@ template class basic_format_args { // It is a separate type rather than an alias to make symbols readable. struct format_args : basic_format_args { template - format_args(Args&&... args) - : basic_format_args(static_cast(args)...) {} + FMT_INLINE format_args(const Args&... args) : basic_format_args(args...) {} }; struct wformat_args : basic_format_args { - template - wformat_args(Args&&... args) - : basic_format_args(static_cast(args)...) {} + using basic_format_args::basic_format_args; }; -template struct is_contiguous : std::false_type {}; - -template -struct is_contiguous> : std::true_type {}; - -template -struct is_contiguous> : std::true_type {}; - -namespace internal { - -template -struct is_contiguous_back_insert_iterator : std::false_type {}; -template -struct is_contiguous_back_insert_iterator> - : is_contiguous {}; - -template struct named_arg_base { - basic_string_view name; - - // Serialized value. - mutable char data[sizeof(basic_format_arg>)]; - - named_arg_base(basic_string_view nm) : name(nm) {} - - template basic_format_arg deserialize() const { - basic_format_arg arg; - std::memcpy(&arg, data, sizeof(basic_format_arg)); - return arg; - } -}; - -struct view {}; - -template -struct named_arg : view, named_arg_base { - const T& value; - - named_arg(basic_string_view name, const T& val) - : named_arg_base(name), value(val) {} -}; +namespace detail { +// Reports a compile-time error if S is not a valid format string. template ::value)> -inline void check_format_string(const S&) { -#if defined(FMT_ENFORCE_COMPILE_STRING) +FMT_INLINE void check_format_string(const S&) { +#ifdef FMT_ENFORCE_COMPILE_STRING static_assert(is_compile_string::value, - "FMT_ENFORCE_COMPILE_STRING requires all format strings to " - "utilize FMT_STRING() or fmt()."); + "FMT_ENFORCE_COMPILE_STRING requires all format strings to use " + "FMT_STRING."); #endif } template ::value)> void check_format_string(S); -template struct bool_pack; -template -using all_true = - std::is_same, bool_pack>; - template > inline format_arg_store, remove_reference_t...> make_args_checked(const S& format_str, const remove_reference_t&... args) { - static_assert( - all_true<(!std::is_base_of>::value || - !std::is_reference::value)...>::value, - "passing views as lvalues is disallowed"); + static_assert(count<(std::is_base_of>::value && + std::is_reference::value)...>() == 0, + "passing views as lvalues is disallowed"); check_format_string(format_str); return {args...}; } -template +template ::value)> std::basic_string vformat( basic_string_view format_str, basic_format_args>> args); +FMT_API std::string vformat(string_view format_str, format_args args); + template -typename buffer_context::iterator vformat_to( +typename FMT_BUFFER_CONTEXT(Char)::iterator vformat_to( buffer& buf, basic_string_view format_str, - basic_format_args>> args); + basic_format_args)> args); template ::value)> @@ -1667,58 +1784,38 @@ FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); #ifndef _WIN32 inline void vprint_mojibake(std::FILE*, string_view, format_args) {} #endif -} // namespace internal - -/** - \rst - Returns a named argument to be used in a formatting function. It should only - be used in a call to a formatting function. - - **Example**:: - - fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); - \endrst - */ -template > -inline internal::named_arg arg(const S& name, const T& arg) { - static_assert(internal::is_string::value, ""); - return {name, arg}; -} - -// Disable nested named arguments, e.g. ``arg("a", arg("b", 42))``. -template -void arg(S, internal::named_arg) = delete; +} // namespace detail /** Formats a string and writes the output to ``out``. */ // GCC 8 and earlier cannot handle std::back_insert_iterator with // vformat_to(...) overload, so SFINAE on iterator type instead. -template , - FMT_ENABLE_IF( - internal::is_contiguous_back_insert_iterator::value)> +template < + typename OutputIt, typename S, typename Char = char_t, + FMT_ENABLE_IF(detail::is_contiguous_back_insert_iterator::value)> OutputIt vformat_to( OutputIt out, const S& format_str, basic_format_args>> args) { - using container = remove_reference_t; - internal::container_buffer buf((internal::get_container(out))); - internal::vformat_to(buf, to_string_view(format_str), args); + auto& c = detail::get_container(out); + detail::container_buffer> buf(c); + detail::vformat_to(buf, to_string_view(format_str), args); return out; } template ::value&& internal::is_string::value)> + is_contiguous::value&& detail::is_string::value)> inline std::back_insert_iterator format_to( std::back_insert_iterator out, const S& format_str, Args&&... args) { return vformat_to(out, to_string_view(format_str), - internal::make_args_checked(format_str, args...)); + detail::make_args_checked(format_str, args...)); } template > -inline std::basic_string vformat( +FMT_INLINE std::basic_string vformat( const S& format_str, basic_format_args>> args) { - return internal::vformat(to_string_view(format_str), args); + return detail::vformat(to_string_view(format_str), args); } /** @@ -1734,10 +1831,9 @@ inline std::basic_string vformat( // Pass char_t as a default template parameter instead of using // std::basic_string> to reduce the symbol size. template > -inline std::basic_string format(const S& format_str, Args&&... args) { - return internal::vformat( - to_string_view(format_str), - internal::make_args_checked(format_str, args...)); +FMT_INLINE std::basic_string format(const S& format_str, Args&&... args) { + const auto& vargs = detail::make_args_checked(format_str, args...); + return detail::vformat(to_string_view(format_str), vargs); } FMT_API void vprint(string_view, format_args); @@ -1756,12 +1852,10 @@ FMT_API void vprint(std::FILE*, string_view, format_args); */ template > inline void print(std::FILE* f, const S& format_str, Args&&... args) { - return internal::is_unicode() - ? vprint(f, to_string_view(format_str), - internal::make_args_checked(format_str, args...)) - : internal::vprint_mojibake( - f, to_string_view(format_str), - internal::make_args_checked(format_str, args...)); + const auto& vargs = detail::make_args_checked(format_str, args...); + return detail::is_unicode() + ? vprint(f, to_string_view(format_str), vargs) + : detail::vprint_mojibake(f, to_string_view(format_str), vargs); } /** @@ -1777,12 +1871,11 @@ inline void print(std::FILE* f, const S& format_str, Args&&... args) { */ template > inline void print(const S& format_str, Args&&... args) { - return internal::is_unicode() - ? vprint(to_string_view(format_str), - internal::make_args_checked(format_str, args...)) - : internal::vprint_mojibake( - stdout, to_string_view(format_str), - internal::make_args_checked(format_str, args...)); + const auto& vargs = detail::make_args_checked(format_str, args...); + return detail::is_unicode() + ? vprint(to_string_view(format_str), vargs) + : detail::vprint_mojibake(stdout, to_string_view(format_str), + vargs); } FMT_END_NAMESPACE diff --git a/oss/fmt/include/fmt/format-inl.h b/oss/fmt/include/fmt/format-inl.h index f632714d81e..d8c9c8a5ee5 100644 --- a/oss/fmt/include/fmt/format-inl.h +++ b/oss/fmt/include/fmt/format-inl.h @@ -15,6 +15,7 @@ #include #include // for std::memmove #include +#include #include "format.h" #if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) @@ -22,8 +23,16 @@ #endif #ifdef _WIN32 +# if !defined(NOMINMAX) && !defined(WIN32_LEAN_AND_MEAN) +# define NOMINMAX +# define WIN32_LEAN_AND_MEAN +# include +# undef WIN32_LEAN_AND_MEAN +# undef NOMINMAX +# else +# include +# endif # include -# include #endif #ifdef _MSC_VER @@ -33,15 +42,19 @@ // Dummy implementations of strerror_r and strerror_s called if corresponding // system functions are not available. -inline fmt::internal::null<> strerror_r(int, char*, ...) { return {}; } -inline fmt::internal::null<> strerror_s(char*, std::size_t, ...) { return {}; } +inline fmt::detail::null<> strerror_r(int, char*, ...) { return {}; } +inline fmt::detail::null<> strerror_s(char*, size_t, ...) { return {}; } FMT_BEGIN_NAMESPACE -namespace internal { +namespace detail { FMT_FUNC void assert_fail(const char* file, int line, const char* message) { - print(stderr, "{}:{}: assertion failed: {}", file, line, message); - std::abort(); + // Use unchecked std::fprintf to avoid triggering another assertion when + // writing to stderr fails + std::fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message); + // Chosen instead of std::abort to satisfy Clang in CUDA mode during device + // code pass. + std::terminate(); } #ifndef _MSC_VER @@ -67,14 +80,14 @@ inline int fmt_snprintf(char* buffer, size_t size, const char* format, ...) { // other - failure // Buffer should be at least of size 1. FMT_FUNC int safe_strerror(int error_code, char*& buffer, - std::size_t buffer_size) FMT_NOEXCEPT { + size_t buffer_size) FMT_NOEXCEPT { FMT_ASSERT(buffer != nullptr && buffer_size != 0, "invalid buffer"); class dispatcher { private: int error_code_; char*& buffer_; - std::size_t buffer_size_; + size_t buffer_size_; // A noop assignment operator to avoid bogus warnings. void operator=(const dispatcher&) {} @@ -97,7 +110,7 @@ FMT_FUNC int safe_strerror(int error_code, char*& buffer, // Handle the case when strerror_r is not available. FMT_MAYBE_UNUSED - int handle(internal::null<>) { + int handle(detail::null<>) { return fallback(strerror_s(buffer_, buffer_size_, error_code_)); } @@ -111,7 +124,7 @@ FMT_FUNC int safe_strerror(int error_code, char*& buffer, #if !FMT_MSC_VER // Fallback to strerror if strerror_r and strerror_s are not available. - int fallback(internal::null<>) { + int fallback(detail::null<>) { errno = 0; buffer_ = strerror(error_code_); return errno; @@ -119,7 +132,7 @@ FMT_FUNC int safe_strerror(int error_code, char*& buffer, #endif public: - dispatcher(int err_code, char*& buf, std::size_t buf_size) + dispatcher(int err_code, char*& buf, size_t buf_size) : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {} int run() { return handle(strerror_r(error_code_, buffer_, buffer_size_)); } @@ -127,7 +140,7 @@ FMT_FUNC int safe_strerror(int error_code, char*& buffer, return dispatcher(error_code, buffer, buffer_size).run(); } -FMT_FUNC void format_error_code(internal::buffer& out, int error_code, +FMT_FUNC void format_error_code(detail::buffer& out, int error_code, string_view message) FMT_NOEXCEPT { // Report error code making sure that the output fits into // inline_buffer_size to avoid dynamic memory allocation and potential @@ -136,20 +149,17 @@ FMT_FUNC void format_error_code(internal::buffer& out, int error_code, static const char SEP[] = ": "; static const char ERROR_STR[] = "error "; // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. - std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; + size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; auto abs_value = static_cast>(error_code); - if (internal::is_negative(error_code)) { + if (detail::is_negative(error_code)) { abs_value = 0 - abs_value; ++error_code_size; } - error_code_size += internal::to_unsigned(internal::count_digits(abs_value)); - internal::writer w(out); - if (message.size() <= inline_buffer_size - error_code_size) { - w.write(message); - w.write(SEP); - } - w.write(ERROR_STR); - w.write(error_code); + error_code_size += detail::to_unsigned(detail::count_digits(abs_value)); + auto it = std::back_inserter(out); + if (message.size() <= inline_buffer_size - error_code_size) + format_to(it, "{}{}", message, SEP); + format_to(it, "{}{}", ERROR_STR, error_code); assert(out.size() <= inline_buffer_size); } @@ -168,10 +178,10 @@ FMT_FUNC void fwrite_fully(const void* ptr, size_t size, size_t count, size_t written = std::fwrite(ptr, size, count, stream); if (written < count) FMT_THROW(system_error(errno, "cannot write to file")); } -} // namespace internal +} // namespace detail #if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) -namespace internal { +namespace detail { template locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { @@ -194,18 +204,16 @@ template FMT_FUNC Char decimal_point_impl(locale_ref loc) { return std::use_facet>(loc.get()) .decimal_point(); } -} // namespace internal +} // namespace detail #else template -FMT_FUNC std::string internal::grouping_impl(locale_ref) { +FMT_FUNC std::string detail::grouping_impl(locale_ref) { return "\03"; } -template -FMT_FUNC Char internal::thousands_sep_impl(locale_ref) { +template FMT_FUNC Char detail::thousands_sep_impl(locale_ref) { return FMT_STATIC_THOUSANDS_SEPARATOR; } -template -FMT_FUNC Char internal::decimal_point_impl(locale_ref) { +template FMT_FUNC Char detail::decimal_point_impl(locale_ref) { return '.'; } #endif @@ -222,9 +230,9 @@ FMT_FUNC void system_error::init(int err_code, string_view format_str, base = std::runtime_error(to_string(buffer)); } -namespace internal { +namespace detail { -template <> FMT_FUNC int count_digits<4>(internal::fallback_uintptr n) { +template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) { // fallback_uintptr is always stored in little endian. int i = static_cast(sizeof(void*)) - 1; while (i > 0 && n.value[i] == 0) --i; @@ -233,12 +241,27 @@ template <> FMT_FUNC int count_digits<4>(internal::fallback_uintptr n) { } template -const char basic_data::digits[] = - "0001020304050607080910111213141516171819" - "2021222324252627282930313233343536373839" - "4041424344454647484950515253545556575859" - "6061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"; +const typename basic_data::digit_pair basic_data::digits[] = { + {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, + {'0', '5'}, {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, + {'1', '0'}, {'1', '1'}, {'1', '2'}, {'1', '3'}, {'1', '4'}, + {'1', '5'}, {'1', '6'}, {'1', '7'}, {'1', '8'}, {'1', '9'}, + {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, {'2', '4'}, + {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'}, + {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, + {'3', '5'}, {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, + {'4', '0'}, {'4', '1'}, {'4', '2'}, {'4', '3'}, {'4', '4'}, + {'4', '5'}, {'4', '6'}, {'4', '7'}, {'4', '8'}, {'4', '9'}, + {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, {'5', '4'}, + {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'}, + {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, + {'6', '5'}, {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, + {'7', '0'}, {'7', '1'}, {'7', '2'}, {'7', '3'}, {'7', '4'}, + {'7', '5'}, {'7', '6'}, {'7', '7'}, {'7', '8'}, {'7', '9'}, + {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, {'8', '4'}, + {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'}, + {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, + {'9', '5'}, {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}}; template const char basic_data::hex_digits[] = "0123456789abcdef"; @@ -317,6 +340,10 @@ const char basic_data::background_color[] = "\x1b[48;2;"; template const char basic_data::reset_color[] = "\x1b[0m"; template const wchar_t basic_data::wreset_color[] = L"\x1b[0m"; template const char basic_data::signs[] = {0, '-', '+', ' '}; +template +const char basic_data::left_padding_shifts[] = {31, 31, 0, 1, 0}; +template +const char basic_data::right_padding_shifts[] = {0, 31, 0, 1, 0}; template struct bits { static FMT_CONSTEXPR_DECL const int value = @@ -576,9 +603,10 @@ class bigint { void operator=(const bigint&) = delete; void assign(const bigint& other) { - bigits_.resize(other.bigits_.size()); + auto size = other.bigits_.size(); + bigits_.resize(size); auto data = other.bigits_.data(); - std::copy(data, data + other.bigits_.size(), bigits_.data()); + std::copy(data, data + size, make_checked(bigits_.data(), size)); exp_ = other.exp_; } @@ -594,7 +622,7 @@ class bigint { int num_bigits() const { return static_cast(bigits_.size()) + exp_; } - bigint& operator<<=(int shift) { + FMT_NOINLINE bigint& operator<<=(int shift) { assert(shift >= 0); exp_ += shift / bigit_bits; shift %= bigit_bits; @@ -1125,7 +1153,7 @@ int snprintf_float(T value, int precision, float_specs specs, precision = (precision >= 0 ? precision : 6) - 1; // Build the format string. - enum { max_format_size = 7 }; // Ths longest format is "%#.*Le". + enum { max_format_size = 7 }; // The longest format is "%#.*Le". char format[max_format_size]; char* format_ptr = format; *format_ptr++ = '%'; @@ -1145,13 +1173,13 @@ int snprintf_float(T value, int precision, float_specs specs, for (;;) { auto begin = buf.data() + offset; auto capacity = buf.capacity() - offset; -#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +#ifdef FMT_FUZZ if (precision > 100000) throw std::runtime_error( "fuzz mode - avoid large allocation inside snprintf"); #endif // Suppress the warning about a nonliteral format string. - // Cannot use auto becase of a bug in MinGW (#1532). + // Cannot use auto because of a bug in MinGW (#1532). int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF; int result = precision >= 0 ? snprintf_ptr(begin, capacity, format, precision, value) @@ -1268,14 +1296,14 @@ FMT_FUNC const char* utf8_decode(const char* buf, uint32_t* c, int* e) { return next; } -} // namespace internal +} // namespace detail -template <> struct formatter { +template <> struct formatter { format_parse_context::iterator parse(format_parse_context& ctx) { return ctx.begin(); } - format_context::iterator format(const internal::bigint& n, + format_context::iterator format(const detail::bigint& n, format_context& ctx) { auto out = ctx.out(); bool first = true; @@ -1289,12 +1317,12 @@ template <> struct formatter { out = format_to(out, "{:08x}", value); } if (n.exp_ > 0) - out = format_to(out, "p{}", n.exp_ * internal::bigint::bigit_bits); + out = format_to(out, "p{}", n.exp_ * detail::bigint::bigit_bits); return out; } }; -FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) { +FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) { auto transcode = [this](const char* p) { auto cp = uint32_t(); auto error = 0; @@ -1325,7 +1353,7 @@ FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) { buffer_.push_back(0); } -FMT_FUNC void format_system_error(internal::buffer& out, int error_code, +FMT_FUNC void format_system_error(detail::buffer& out, int error_code, string_view message) FMT_NOEXCEPT { FMT_TRY { memory_buffer buf; @@ -1333,12 +1361,9 @@ FMT_FUNC void format_system_error(internal::buffer& out, int error_code, for (;;) { char* system_message = &buf[0]; int result = - internal::safe_strerror(error_code, system_message, buf.size()); + detail::safe_strerror(error_code, system_message, buf.size()); if (result == 0) { - internal::writer w(out); - w.write(message); - w.write(": "); - w.write(system_message); + format_to(std::back_inserter(out), "{}: {}", message, system_message); return; } if (result != ERANGE) @@ -1350,7 +1375,7 @@ FMT_FUNC void format_system_error(internal::buffer& out, int error_code, format_error_code(out, error_code, message); } -FMT_FUNC void internal::error_handler::on_error(const char* message) { +FMT_FUNC void detail::error_handler::on_error(const char* message) { FMT_THROW(format_error(message)); } @@ -1359,14 +1384,39 @@ FMT_FUNC void report_system_error(int error_code, report_error(format_system_error, error_code, message); } +struct stringifier { + template FMT_INLINE std::string operator()(T value) const { + return to_string(value); + } + std::string operator()(basic_format_arg::handle h) const { + memory_buffer buf; + detail::buffer& base = buf; + format_parse_context parse_ctx({}); + format_context format_ctx(std::back_inserter(base), {}, {}); + h.format(parse_ctx, format_ctx); + return to_string(buf); + } +}; + +FMT_FUNC std::string detail::vformat(string_view format_str, format_args args) { + if (format_str.size() == 2 && equal2(format_str.data(), "{}")) { + auto arg = args.get(0); + if (!arg) error_handler().on_error("argument not found"); + return visit_format_arg(stringifier(), arg); + } + memory_buffer buffer; + detail::vformat_to(buffer, format_str, args); + return to_string(buffer); +} + FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) { memory_buffer buffer; - internal::vformat_to(buffer, format_str, - basic_format_args>(args)); + detail::vformat_to(buffer, format_str, + basic_format_args>(args)); #ifdef _WIN32 auto fd = _fileno(f); if (_isatty(fd)) { - internal::utf8_to_utf16 u16(string_view(buffer.data(), buffer.size())); + detail::utf8_to_utf16 u16(string_view(buffer.data(), buffer.size())); auto written = DWORD(); if (!WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), u16.c_str(), static_cast(u16.size()), &written, @@ -1376,16 +1426,16 @@ FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) { return; } #endif - internal::fwrite_fully(buffer.data(), 1, buffer.size(), f); + detail::fwrite_fully(buffer.data(), 1, buffer.size(), f); } #ifdef _WIN32 // Print assuming legacy (non-Unicode) encoding. -FMT_FUNC void internal::vprint_mojibake(std::FILE* f, string_view format_str, - format_args args) { +FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str, + format_args args) { memory_buffer buffer; - internal::vformat_to(buffer, format_str, - basic_format_args>(args)); + detail::vformat_to(buffer, format_str, + basic_format_args>(args)); fwrite_fully(buffer.data(), 1, buffer.size(), f); } #endif diff --git a/oss/fmt/include/fmt/format.h b/oss/fmt/include/fmt/format.h index 4e96539fa2a..ddf086f8f71 100644 --- a/oss/fmt/include/fmt/format.h +++ b/oss/fmt/include/fmt/format.h @@ -43,10 +43,6 @@ #include "core.h" -#ifdef FMT_DEPRECATED_INCLUDE_OS -# include "os.h" -#endif - #ifdef __INTEL_COMPILER # define FMT_ICC_VERSION __INTEL_COMPILER #elif defined(__ICL) @@ -76,7 +72,8 @@ #if __cplusplus == 201103L || __cplusplus == 201402L # if defined(__clang__) # define FMT_FALLTHROUGH [[clang::fallthrough]] -# elif FMT_GCC_VERSION >= 700 && !defined(__PGI) +# elif FMT_GCC_VERSION >= 700 && !defined(__PGI) && \ + (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) # define FMT_FALLTHROUGH [[gnu::fallthrough]] # else # define FMT_FALLTHROUGH @@ -88,20 +85,28 @@ # define FMT_FALLTHROUGH #endif +#ifndef FMT_MAYBE_UNUSED +# if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused) +# define FMT_MAYBE_UNUSED [[maybe_unused]] +# else +# define FMT_MAYBE_UNUSED +# endif +#endif + #ifndef FMT_THROW # if FMT_EXCEPTIONS # if FMT_MSC_VER || FMT_NVCC FMT_BEGIN_NAMESPACE -namespace internal { +namespace detail { template inline void do_throw(const Exception& x) { // Silence unreachable code warnings in MSVC and NVCC because these // are nearly impossible to fix in a generic code. volatile bool b = true; if (b) throw x; } -} // namespace internal +} // namespace detail FMT_END_NAMESPACE -# define FMT_THROW(x) internal::do_throw(x) +# define FMT_THROW(x) detail::do_throw(x) # else # define FMT_THROW(x) throw x # endif @@ -123,11 +128,10 @@ FMT_END_NAMESPACE #endif #ifndef FMT_USE_USER_DEFINED_LITERALS -// For Intel and NVIDIA compilers both they and the system gcc/msc support UDLs. -# if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 407 || \ - FMT_MSC_VER >= 1900) && \ - (!(FMT_ICC_VERSION || FMT_CUDA_VERSION) || FMT_ICC_VERSION >= 1500 || \ - FMT_CUDA_VERSION >= 700) +// EDG based compilers (Intel, NVIDIA, Elbrus, etc), GCC and MSVC support UDLs. +# if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 407 || \ + FMT_MSC_VER >= 1900) && \ + (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= /* UDL feature */ 480) # define FMT_USE_USER_DEFINED_LITERALS 1 # else # define FMT_USE_USER_DEFINED_LITERALS 0 @@ -135,12 +139,11 @@ FMT_END_NAMESPACE #endif #ifndef FMT_USE_UDL_TEMPLATE -// EDG front end based compilers (icc, nvcc) and GCC < 6.4 do not propertly +// EDG frontend based compilers (icc, nvcc, etc) and GCC < 6.4 do not properly // support UDL templates and GCC >= 9 warns about them. -# if FMT_USE_USER_DEFINED_LITERALS && FMT_ICC_VERSION == 0 && \ - FMT_CUDA_VERSION == 0 && \ - ((FMT_GCC_VERSION >= 604 && FMT_GCC_VERSION <= 900 && \ - __cplusplus >= 201402L) || \ +# if FMT_USE_USER_DEFINED_LITERALS && \ + (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 501) && \ + ((FMT_GCC_VERSION >= 604 && __cplusplus >= 201402L) || \ FMT_CLANG_VERSION >= 304) # define FMT_USE_UDL_TEMPLATE 1 # else @@ -176,7 +179,7 @@ FMT_END_NAMESPACE # include // _BitScanReverse, _BitScanReverse64 FMT_BEGIN_NAMESPACE -namespace internal { +namespace detail { // Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. # ifndef __clang__ # pragma intrinsic(_BitScanReverse) @@ -189,10 +192,10 @@ inline uint32_t clz(uint32_t x) { // Static analysis complains about using uninitialized data // "r", but the only way that can happen is if "x" is 0, // which the callers guarantee to not happen. -# pragma warning(suppress : 6102) + FMT_SUPPRESS_MSC_WARNING(6102) return 31 - r; } -# define FMT_BUILTIN_CLZ(n) internal::clz(n) +# define FMT_BUILTIN_CLZ(n) detail::clz(n) # if defined(_WIN64) && !defined(__clang__) # pragma intrinsic(_BitScanReverse64) @@ -214,26 +217,21 @@ inline uint32_t clzll(uint64_t x) { // Static analysis complains about using uninitialized data // "r", but the only way that can happen is if "x" is 0, // which the callers guarantee to not happen. -# pragma warning(suppress : 6102) + FMT_SUPPRESS_MSC_WARNING(6102) return 63 - r; } -# define FMT_BUILTIN_CLZLL(n) internal::clzll(n) -} // namespace internal +# define FMT_BUILTIN_CLZLL(n) detail::clzll(n) +} // namespace detail FMT_END_NAMESPACE #endif // Enable the deprecated numeric alignment. -#ifndef FMT_NUMERIC_ALIGN -# define FMT_NUMERIC_ALIGN 1 -#endif - -// Enable the deprecated percent specifier. -#ifndef FMT_DEPRECATED_PERCENT -# define FMT_DEPRECATED_PERCENT 0 +#ifndef FMT_DEPRECATED_NUMERIC_ALIGN +# define FMT_DEPRECATED_NUMERIC_ALIGN 0 #endif FMT_BEGIN_NAMESPACE -namespace internal { +namespace detail { // An equivalent of `*reinterpret_cast(&source)` that doesn't have // undefined behavior (e.g. due to type aliasing). @@ -290,9 +288,23 @@ template <> constexpr int num_bits() { std::numeric_limits::digits); } +FMT_INLINE void assume(bool condition) { + (void)condition; +#if FMT_HAS_BUILTIN(__builtin_assume) + __builtin_assume(condition); +#endif +} + +// A workaround for gcc 4.8 to make void_t work in a SFINAE context. +template struct void_t_impl { using type = void; }; + +template +using void_t = typename detail::void_t_impl::type; + // An approximation of iterator_t for pre-C++20 systems. template using iterator_t = decltype(std::begin(std::declval())); +template using sentinel_t = decltype(std::end(std::declval())); // Detect the iterator category of *any* given type in a SFINAE-friendly way. // Unfortunately, older implementations of std::iterator_traits are not safe @@ -339,25 +351,39 @@ inline typename Container::value_type* get_data(Container& c) { #if defined(_SECURE_SCL) && _SECURE_SCL // Make a checked iterator to avoid MSVC warnings. template using checked_ptr = stdext::checked_array_iterator; -template checked_ptr make_checked(T* p, std::size_t size) { +template checked_ptr make_checked(T* p, size_t size) { return {p, size}; } #else template using checked_ptr = T*; -template inline T* make_checked(T* p, std::size_t) { return p; } +template inline T* make_checked(T* p, size_t) { return p; } #endif template ::value)> -inline checked_ptr reserve( - std::back_insert_iterator& it, std::size_t n) { +#if FMT_CLANG_VERSION +__attribute__((no_sanitize("undefined"))) +#endif +inline checked_ptr +reserve(std::back_insert_iterator it, size_t n) { Container& c = get_container(it); - std::size_t size = c.size(); + size_t size = c.size(); c.resize(size + n); return make_checked(get_data(c) + size, n); } +template inline Iterator& reserve(Iterator& it, size_t) { + return it; +} + +template ::value)> +inline std::back_insert_iterator base_iterator( + std::back_insert_iterator& it, + checked_ptr) { + return it; +} + template -inline Iterator& reserve(Iterator& it, std::size_t) { +inline Iterator base_iterator(Iterator, Iterator it) { return it; } @@ -365,7 +391,7 @@ inline Iterator& reserve(Iterator& it, std::size_t) { // discards them. class counting_iterator { private: - std::size_t count_; + size_t count_; public: using iterator_category = std::output_iterator_tag; @@ -380,7 +406,7 @@ class counting_iterator { counting_iterator() : count_(0) {} - std::size_t count() const { return count_; } + size_t count() const { return count_; } counting_iterator& operator++() { ++count_; @@ -399,10 +425,10 @@ class counting_iterator { template class truncating_iterator_base { protected: OutputIt out_; - std::size_t limit_; - std::size_t count_; + size_t limit_; + size_t count_; - truncating_iterator_base(OutputIt out, std::size_t limit) + truncating_iterator_base(OutputIt out, size_t limit) : out_(out), limit_(limit), count_(0) {} public: @@ -415,7 +441,7 @@ template class truncating_iterator_base { truncating_iterator_base; // Mark iterator as checked. OutputIt base() const { return out_; } - std::size_t count() const { return count_; } + size_t count() const { return count_; } }; // An output iterator that truncates the output and counts the number of objects @@ -433,7 +459,7 @@ class truncating_iterator public: using value_type = typename truncating_iterator_base::value_type; - truncating_iterator(OutputIt out, std::size_t limit) + truncating_iterator(OutputIt out, size_t limit) : truncating_iterator_base(out, limit) {} truncating_iterator& operator++() { @@ -456,7 +482,7 @@ template class truncating_iterator : public truncating_iterator_base { public: - truncating_iterator(OutputIt out, std::size_t limit) + truncating_iterator(OutputIt out, size_t limit) : truncating_iterator_base(out, limit) {} template truncating_iterator& operator=(T val) { @@ -469,22 +495,6 @@ class truncating_iterator truncating_iterator& operator*() { return *this; } }; -// A range with the specified output iterator and value type. -template -class output_range { - private: - OutputIt it_; - - public: - using value_type = T; - using iterator = OutputIt; - struct sentinel {}; - - explicit output_range(OutputIt it) : it_(it) {} - OutputIt begin() const { return it_; } - sentinel end() const { return {}; } // Sentinel is not used yet. -}; - template inline size_t count_code_points(basic_string_view s) { return s.size(); @@ -523,8 +533,6 @@ inline size_t code_point_index(basic_string_view s, size_t n) { return s.size(); } -inline char8_type to_char8_t(char c) { return static_cast(c); } - template using needs_conversion = bool_constant< std::is_same::value_type, @@ -540,7 +548,8 @@ OutputIt copy_str(InputIt begin, InputIt end, OutputIt it) { template ::value)> OutputIt copy_str(InputIt begin, InputIt end, OutputIt it) { - return std::transform(begin, end, it, to_char8_t); + return std::transform(begin, end, it, + [](char c) { return static_cast(c); }); } #ifndef FMT_USE_GRISU @@ -555,43 +564,13 @@ template constexpr bool use_grisu() { template template void buffer::append(const U* begin, const U* end) { - std::size_t new_size = size_ + to_unsigned(end - begin); + size_t new_size = size_ + to_unsigned(end - begin); reserve(new_size); - std::uninitialized_copy(begin, end, make_checked(ptr_, capacity_) + size_); + std::uninitialized_copy(begin, end, + make_checked(ptr_ + size_, capacity_ - size_)); size_ = new_size; } -} // namespace internal - -// A range with an iterator appending to a buffer. -template -class buffer_range : public internal::output_range< - std::back_insert_iterator>, T> { - public: - using iterator = std::back_insert_iterator>; - using internal::output_range::output_range; - buffer_range(internal::buffer& buf) - : internal::output_range(std::back_inserter(buf)) {} -}; - -class FMT_DEPRECATED u8string_view - : public basic_string_view { - public: - u8string_view(const char* s) - : basic_string_view( - reinterpret_cast(s)) {} - u8string_view(const char* s, size_t count) FMT_NOEXCEPT - : basic_string_view( - reinterpret_cast(s), count) {} -}; - -#if FMT_USE_USER_DEFINED_LITERALS -inline namespace literals { -FMT_DEPRECATED inline basic_string_view operator"" _u( - const char* s, std::size_t n) { - return {reinterpret_cast(s), n}; -} -} // namespace literals -#endif +} // namespace detail // The number of characters to store in the basic_memory_buffer object itself // to avoid dynamic memory allocation. @@ -626,27 +605,30 @@ enum { inline_buffer_size = 500 }; The output can be converted to an ``std::string`` with ``to_string(out)``. \endrst */ -template > -class basic_memory_buffer : private Allocator, public internal::buffer { +class basic_memory_buffer : public detail::buffer { private: T store_[SIZE]; + // Don't inherit from Allocator avoid generating type_info for it. + Allocator alloc_; + // Deallocate memory allocated by the buffer. void deallocate() { T* data = this->data(); - if (data != store_) Allocator::deallocate(data, this->capacity()); + if (data != store_) alloc_.deallocate(data, this->capacity()); } protected: - void grow(std::size_t size) FMT_OVERRIDE; + void grow(size_t size) FMT_OVERRIDE; public: using value_type = T; using const_reference = const T&; explicit basic_memory_buffer(const Allocator& alloc = Allocator()) - : Allocator(alloc) { + : alloc_(alloc) { this->set(store_, SIZE); } ~basic_memory_buffer() FMT_OVERRIDE { deallocate(); } @@ -654,14 +636,13 @@ class basic_memory_buffer : private Allocator, public internal::buffer { private: // Move data from other to this buffer. void move(basic_memory_buffer& other) { - Allocator &this_alloc = *this, &other_alloc = other; - this_alloc = std::move(other_alloc); + alloc_ = std::move(other.alloc_); T* data = other.data(); - std::size_t size = other.size(), capacity = other.capacity(); + size_t size = other.size(), capacity = other.capacity(); if (data == other.store_) { this->set(store_, capacity); std::uninitialized_copy(other.store_, other.store_ + size, - internal::make_checked(store_, capacity)); + detail::make_checked(store_, capacity)); } else { this->set(data, capacity); // Set pointer to the inline array so that delete is not called @@ -693,32 +674,37 @@ class basic_memory_buffer : private Allocator, public internal::buffer { } // Returns a copy of the allocator associated with this buffer. - Allocator get_allocator() const { return *this; } + Allocator get_allocator() const { return alloc_; } }; -template -void basic_memory_buffer::grow(std::size_t size) { -#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION - if (size > 1000) throw std::runtime_error("fuzz mode - won't grow that much"); +template +void basic_memory_buffer::grow(size_t size) { +#ifdef FMT_FUZZ + if (size > 5000) throw std::runtime_error("fuzz mode - won't grow that much"); #endif - std::size_t old_capacity = this->capacity(); - std::size_t new_capacity = old_capacity + old_capacity / 2; + size_t old_capacity = this->capacity(); + size_t new_capacity = old_capacity + old_capacity / 2; if (size > new_capacity) new_capacity = size; T* old_data = this->data(); - T* new_data = std::allocator_traits::allocate(*this, new_capacity); + T* new_data = + std::allocator_traits::allocate(alloc_, new_capacity); // The following code doesn't throw, so the raw pointer above doesn't leak. std::uninitialized_copy(old_data, old_data + this->size(), - internal::make_checked(new_data, new_capacity)); + detail::make_checked(new_data, new_capacity)); this->set(new_data, new_capacity); // deallocate must not throw according to the standard, but even if it does, // the buffer already uses the new storage and will deallocate it in // destructor. - if (old_data != store_) Allocator::deallocate(old_data, old_capacity); + if (old_data != store_) alloc_.deallocate(old_data, old_capacity); } using memory_buffer = basic_memory_buffer; using wmemory_buffer = basic_memory_buffer; +template +struct is_contiguous> : std::true_type { +}; + /** A formatting error such as invalid format string. */ FMT_CLASS_API class FMT_API format_error : public std::runtime_error { @@ -733,7 +719,7 @@ class FMT_API format_error : public std::runtime_error { ~format_error() FMT_NOEXCEPT FMT_OVERRIDE; }; -namespace internal { +namespace detail { // Returns true if value is negative, false otherwise. // Same as `value < 0` but doesn't produce warnings if T is an unsigned type. @@ -767,16 +753,22 @@ template struct FMT_EXTERN_TEMPLATE_API basic_data { static const uint64_t zero_or_powers_of_10_64[]; static const uint64_t pow10_significands[]; static const int16_t pow10_exponents[]; - static const char digits[]; + // GCC generates slightly better code for pairs than chars. + using digit_pair = char[2]; + static const digit_pair digits[]; static const char hex_digits[]; static const char foreground_color[]; static const char background_color[]; static const char reset_color[5]; static const wchar_t wreset_color[5]; static const char signs[]; + static const char left_padding_shifts[5]; + static const char right_padding_shifts[5]; }; +#ifndef FMT_EXPORTED FMT_EXTERN template struct basic_data; +#endif // This is a struct rather than an alias to avoid shadowing warnings in gcc. struct data : basic_data<> {}; @@ -834,7 +826,7 @@ template inline int count_digits(UInt n) { return num_digits; } -template <> int count_digits<4>(internal::fallback_uintptr n); +template <> int count_digits<4>(detail::fallback_uintptr n); #if FMT_GCC_VERSION || FMT_CLANG_VERSION # define FMT_ALWAYS_INLINE inline __attribute__((always_inline)) @@ -850,6 +842,12 @@ inline int count_digits(uint32_t n) { } #endif +template constexpr int digits10() FMT_NOEXCEPT { + return std::numeric_limits::digits10; +} +template <> constexpr int digits10() FMT_NOEXCEPT { return 38; } +template <> constexpr int digits10() FMT_NOEXCEPT { return 38; } + template FMT_API std::string grouping_impl(locale_ref loc); template inline std::string grouping(locale_ref loc) { return grouping_impl(loc); @@ -874,57 +872,61 @@ template <> inline wchar_t decimal_point(locale_ref loc) { return decimal_point_impl(loc); } -// Formats a decimal unsigned integer value writing into buffer. -// add_thousands_sep is called after writing each char to add a thousands -// separator if necessary. -template -inline Char* format_decimal(Char* buffer, UInt value, int num_digits, - F add_thousands_sep) { - FMT_ASSERT(num_digits >= 0, "invalid digit count"); - buffer += num_digits; - Char* end = buffer; +// Compares two characters for equality. +template bool equal2(const Char* lhs, const char* rhs) { + return lhs[0] == rhs[0] && lhs[1] == rhs[1]; +} +inline bool equal2(const char* lhs, const char* rhs) { + return memcmp(lhs, rhs, 2) == 0; +} + +// Copies two characters from src to dst. +template void copy2(Char* dst, const char* src) { + *dst++ = static_cast(*src++); + *dst = static_cast(*src); +} +inline void copy2(char* dst, const char* src) { memcpy(dst, src, 2); } + +template struct format_decimal_result { + Iterator begin; + Iterator end; +}; + +// Formats a decimal unsigned integer value writing into out pointing to a +// buffer of specified size. The caller must ensure that the buffer is large +// enough. +template +inline format_decimal_result format_decimal(Char* out, UInt value, + int size) { + FMT_ASSERT(size >= count_digits(value), "invalid digit count"); + out += size; + Char* end = out; while (value >= 100) { // Integer division is slow so do it for a group of two digits instead // of for every digit. The idea comes from the talk by Alexandrescu // "Three Optimization Tips for C++". See speed-test for a comparison. - auto index = static_cast((value % 100) * 2); + out -= 2; + copy2(out, data::digits[value % 100]); value /= 100; - *--buffer = static_cast(data::digits[index + 1]); - add_thousands_sep(buffer); - *--buffer = static_cast(data::digits[index]); - add_thousands_sep(buffer); } if (value < 10) { - *--buffer = static_cast('0' + value); - return end; + *--out = static_cast('0' + value); + return {out, end}; } - auto index = static_cast(value * 2); - *--buffer = static_cast(data::digits[index + 1]); - add_thousands_sep(buffer); - *--buffer = static_cast(data::digits[index]); - return end; + out -= 2; + copy2(out, data::digits[value]); + return {out, end}; } -template constexpr int digits10() FMT_NOEXCEPT { - return std::numeric_limits::digits10; -} -template <> constexpr int digits10() FMT_NOEXCEPT { return 38; } -template <> constexpr int digits10() FMT_NOEXCEPT { return 38; } - -template -inline Iterator format_decimal(Iterator out, UInt value, int num_digits, - F add_thousands_sep) { - FMT_ASSERT(num_digits >= 0, "invalid digit count"); +template >::value)> +inline format_decimal_result format_decimal(Iterator out, UInt value, + int num_digits) { // Buffer should be large enough to hold all digits (<= digits10 + 1). enum { max_size = digits10() + 1 }; Char buffer[2 * max_size]; - auto end = format_decimal(buffer, value, num_digits, add_thousands_sep); - return internal::copy_str(buffer, end, out); -} - -template -inline It format_decimal(It out, UInt value, int num_digits) { - return format_decimal(out, value, num_digits, [](Char*) {}); + auto end = format_decimal(buffer, value, num_digits).end; + return {out, detail::copy_str(buffer, end, out)}; } template @@ -942,7 +944,7 @@ inline Char* format_uint(Char* buffer, UInt value, int num_digits, } template -Char* format_uint(Char* buffer, internal::fallback_uintptr n, int num_digits, +Char* format_uint(Char* buffer, detail::fallback_uintptr n, int num_digits, bool = false) { auto char_digits = std::numeric_limits::digits / 4; int start = (num_digits + char_digits - 1) / char_digits - 1; @@ -968,7 +970,7 @@ inline It format_uint(It out, UInt value, int num_digits, bool upper = false) { // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1). char buffer[num_bits() / BASE_BITS + 1]; format_uint(buffer, value, num_digits, upper); - return internal::copy_str(buffer, buffer + num_digits, out); + return detail::copy_str(buffer, buffer + num_digits, out); } // A converter from UTF-8 to UTF-16. @@ -1019,7 +1021,7 @@ template struct fill_t { return fill; } }; -} // namespace internal +} // namespace detail // We cannot use enum classes as bit fields because of a gcc bug // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414. @@ -1041,7 +1043,7 @@ template struct basic_format_specs { align_t align : 4; sign_t sign : 3; bool alt : 1; // Alternate form ('#'). - internal::fill_t fill; + detail::fill_t fill; constexpr basic_format_specs() : width(0), @@ -1050,12 +1052,12 @@ template struct basic_format_specs { align(align::none), sign(sign::none), alt(false), - fill(internal::fill_t::make()) {} + fill(detail::fill_t::make()) {} }; using format_specs = basic_format_specs; -namespace internal { +namespace detail { // A floating-point presentation format. enum class float_format : unsigned char { @@ -1071,7 +1073,6 @@ struct float_specs { sign_t sign : 8; bool upper : 1; bool locale : 1; - bool percent : 1; bool binary32 : 1; bool use_grisu : 1; bool showpoint : 1; @@ -1087,12 +1088,12 @@ template It write_exponent(int exp, It it) { *it++ = static_cast('+'); } if (exp >= 100) { - const char* top = data::digits + (exp / 100) * 2; + const char* top = data::digits[exp / 100]; if (exp >= 1000) *it++ = static_cast(top[0]); *it++ = static_cast(top[1]); exp %= 100; } - const char* d = data::digits + exp * 2; + const char* d = data::digits[exp]; *it++ = static_cast(d[0]); *it++ = static_cast(d[1]); return it; @@ -1134,8 +1135,8 @@ template class float_writer { *it++ = static_cast('0'); return it; } -#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION - if (num_zeros > 1000) +#ifdef FMT_FUZZ + if (num_zeros > 5000) throw std::runtime_error("fuzz mode - avoiding excessive cpu use"); #endif it = std::fill_n(it, num_zeros, static_cast('0')); @@ -1198,11 +1199,10 @@ template class float_writer { } size_t size() const { return size_; } - size_t width() const { return size(); } - template void operator()(It&& it) { + template It operator()(It it) const { if (specs_.sign) *it++ = static_cast(data::signs[specs_.sign]); - it = prettify(it); + return prettify(it); } }; @@ -1235,10 +1235,15 @@ FMT_CONSTEXPR void handle_int_type_spec(char spec, Handler&& handler) { case 'o': handler.on_oct(); break; +#ifdef FMT_DEPRECATED_N_SPECIFIER case 'n': +#endif case 'L': handler.on_num(); break; + case 'c': + handler.on_chr(); + break; default: handler.on_error(); } @@ -1274,19 +1279,16 @@ FMT_CONSTEXPR float_specs parse_float_type_spec( result.format = float_format::fixed; result.showpoint |= specs.precision != 0; break; -#if FMT_DEPRECATED_PERCENT - case '%': - result.format = float_format::fixed; - result.percent = true; - break; -#endif case 'A': result.upper = true; FMT_FALLTHROUGH; case 'a': result.format = float_format::hex; break; +#ifdef FMT_DEPRECATED_N_SPECIFIER case 'n': +#endif + case 'L': result.locale = true; break; default: @@ -1335,6 +1337,7 @@ template class int_type_checker : private ErrorHandler { FMT_CONSTEXPR void on_bin() {} FMT_CONSTEXPR void on_oct() {} FMT_CONSTEXPR void on_num() {} + FMT_CONSTEXPR void on_chr() {} FMT_CONSTEXPR void on_error() { ErrorHandler::on_error("invalid type specifier"); @@ -1366,38 +1369,6 @@ class cstring_type_checker : public ErrorHandler { FMT_CONSTEXPR void on_pointer() {} }; -template -void arg_map::init(const basic_format_args& args) { - if (map_) return; - map_ = new entry[internal::to_unsigned(args.max_size())]; - if (args.is_packed()) { - for (int i = 0;; ++i) { - internal::type arg_type = args.type(i); - if (arg_type == internal::type::none_type) return; - if (arg_type == internal::type::named_arg_type) - push_back(args.values_[i]); - } - } - for (int i = 0, n = args.max_size(); i < n; ++i) { - auto type = args.args_[i].type_; - if (type == internal::type::named_arg_type) push_back(args.args_[i].value_); - } -} - -template struct nonfinite_writer { - sign_t sign; - const char* str; - static constexpr size_t str_size = 3; - - size_t size() const { return str_size + (sign ? 1 : 0); } - size_t width() const { return size(); } - - template void operator()(It&& it) const { - if (sign) *it++ = static_cast(data::signs[sign]); - it = copy_str(str, str + str_size, it); - } -}; - template FMT_NOINLINE OutputIt fill(OutputIt it, size_t n, const fill_t& fill) { auto fill_size = fill.size(); @@ -1406,375 +1377,470 @@ FMT_NOINLINE OutputIt fill(OutputIt it, size_t n, const fill_t& fill) { return it; } -// This template provides operations for formatting and writing data into a -// character range. -template class basic_writer { - public: - using char_type = typename Range::value_type; - using iterator = typename Range::iterator; - using format_specs = basic_format_specs; - - private: - iterator out_; // Output iterator. - locale_ref locale_; - - // Attempts to reserve space for n extra characters in the output range. - // Returns a pointer to the reserved range or a reference to out_. - auto reserve(std::size_t n) -> decltype(internal::reserve(out_, n)) { - return internal::reserve(out_, n); - } - - template struct padded_int_writer { - size_t size_; - string_view prefix; - char_type fill; - std::size_t padding; - F f; - - size_t size() const { return size_; } - size_t width() const { return size_; } - - template void operator()(It&& it) const { - if (prefix.size() != 0) - it = copy_str(prefix.begin(), prefix.end(), it); - it = std::fill_n(it, padding, fill); - f(it); - } - }; - - // Writes an integer in the format - // - // where are written by f(it). - template - void write_int(int num_digits, string_view prefix, format_specs specs, F f) { - std::size_t size = prefix.size() + to_unsigned(num_digits); - char_type fill = specs.fill[0]; - std::size_t padding = 0; +// Writes the output of f, padded according to format specifications in specs. +// size: output size in code units. +// width: output display width in (terminal) column positions. +template +inline OutputIt write_padded(OutputIt out, + const basic_format_specs& specs, size_t size, + size_t width, const F& f) { + static_assert(align == align::left || align == align::right, ""); + unsigned spec_width = to_unsigned(specs.width); + size_t padding = spec_width > width ? spec_width - width : 0; + auto* shifts = align == align::left ? data::left_padding_shifts + : data::right_padding_shifts; + size_t left_padding = padding >> shifts[specs.align]; + auto it = reserve(out, size + padding * specs.fill.size()); + it = fill(it, left_padding, specs.fill); + it = f(it); + it = fill(it, padding - left_padding, specs.fill); + return base_iterator(out, it); +} + +template +inline OutputIt write_padded(OutputIt out, + const basic_format_specs& specs, size_t size, + const F& f) { + return write_padded(out, specs, size, size, f); +} + +template +OutputIt write_bytes(OutputIt out, string_view bytes, + const basic_format_specs& specs) { + using iterator = remove_reference_t; + return write_padded(out, specs, bytes.size(), [bytes](iterator it) { + const char* data = bytes.data(); + return copy_str(data, data + bytes.size(), it); + }); +} + +// Data for write_int that doesn't depend on output iterator type. It is used to +// avoid template code bloat. +template struct write_int_data { + size_t size; + size_t padding; + + write_int_data(int num_digits, string_view prefix, + const basic_format_specs& specs) + : size(prefix.size() + to_unsigned(num_digits)), padding(0) { if (specs.align == align::numeric) { - auto unsiged_width = to_unsigned(specs.width); - if (unsiged_width > size) { - padding = unsiged_width - size; - size = unsiged_width; + auto width = to_unsigned(specs.width); + if (width > size) { + padding = width - size; + size = width; } } else if (specs.precision > num_digits) { size = prefix.size() + to_unsigned(specs.precision); padding = to_unsigned(specs.precision - num_digits); - fill = static_cast('0'); } - if (specs.align == align::none) specs.align = align::right; - write_padded(specs, padded_int_writer{size, prefix, fill, padding, f}); } +}; - // Writes a decimal integer. - template void write_decimal(Int value) { - auto abs_value = static_cast>(value); - bool negative = is_negative(value); - // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer. - if (negative) abs_value = ~abs_value + 1; - int num_digits = count_digits(abs_value); - auto&& it = reserve((negative ? 1 : 0) + static_cast(num_digits)); - if (negative) *it++ = static_cast('-'); - it = format_decimal(it, abs_value, num_digits); - } - - // The handle_int_type_spec handler that writes an integer. - template struct int_writer { - using unsigned_type = uint32_or_64_or_128_t; - - basic_writer& writer; - const Specs& specs; - unsigned_type abs_value; - char prefix[4]; - unsigned prefix_size; - - string_view get_prefix() const { return string_view(prefix, prefix_size); } - - int_writer(basic_writer& w, Int value, const Specs& s) - : writer(w), - specs(s), - abs_value(static_cast(value)), - prefix_size(0) { - if (is_negative(value)) { - prefix[0] = '-'; - ++prefix_size; - abs_value = 0 - abs_value; - } else if (specs.sign != sign::none && specs.sign != sign::minus) { - prefix[0] = specs.sign == sign::plus ? '+' : ' '; - ++prefix_size; - } +// Writes an integer in the format +// +// where are written by f(it). +template +OutputIt write_int(OutputIt out, int num_digits, string_view prefix, + const basic_format_specs& specs, F f) { + auto data = write_int_data(num_digits, prefix, specs); + using iterator = remove_reference_t; + return write_padded(out, specs, data.size, [=](iterator it) { + if (prefix.size() != 0) + it = copy_str(prefix.begin(), prefix.end(), it); + it = std::fill_n(it, data.padding, static_cast('0')); + return f(it); + }); +} + +template +OutputIt write(OutputIt out, basic_string_view s, + const basic_format_specs& specs) { + auto data = s.data(); + auto size = s.size(); + if (specs.precision >= 0 && to_unsigned(specs.precision) < size) + size = code_point_index(s, to_unsigned(specs.precision)); + auto width = specs.width != 0 + ? count_code_points(basic_string_view(data, size)) + : 0; + using iterator = remove_reference_t; + return write_padded(out, specs, size, width, [=](iterator it) { + return copy_str(data, data + size, it); + }); +} + +// The handle_int_type_spec handler that writes an integer. +template struct int_writer { + OutputIt out; + locale_ref locale; + const basic_format_specs& specs; + UInt abs_value; + char prefix[4]; + unsigned prefix_size; + + using iterator = + remove_reference_t(), 0))>; + + string_view get_prefix() const { return string_view(prefix, prefix_size); } + + template + int_writer(OutputIt output, locale_ref loc, Int value, + const basic_format_specs& s) + : out(output), + locale(loc), + specs(s), + abs_value(static_cast(value)), + prefix_size(0) { + static_assert(std::is_same, UInt>::value, ""); + if (is_negative(value)) { + prefix[0] = '-'; + ++prefix_size; + abs_value = 0 - abs_value; + } else if (specs.sign != sign::none && specs.sign != sign::minus) { + prefix[0] = specs.sign == sign::plus ? '+' : ' '; + ++prefix_size; } + } - struct dec_writer { - unsigned_type abs_value; - int num_digits; - - template void operator()(It&& it) const { - it = internal::format_decimal(it, abs_value, num_digits); - } - }; + void on_dec() { + auto num_digits = count_digits(abs_value); + out = write_int( + out, num_digits, get_prefix(), specs, [this, num_digits](iterator it) { + return format_decimal(it, abs_value, num_digits).end; + }); + } - void on_dec() { - int num_digits = count_digits(abs_value); - writer.write_int(num_digits, get_prefix(), specs, - dec_writer{abs_value, num_digits}); + void on_hex() { + if (specs.alt) { + prefix[prefix_size++] = '0'; + prefix[prefix_size++] = specs.type; } - - struct hex_writer { - int_writer& self; - int num_digits; - - template void operator()(It&& it) const { - it = format_uint<4, char_type>(it, self.abs_value, num_digits, - self.specs.type != 'x'); - } - }; - - void on_hex() { - if (specs.alt) { - prefix[prefix_size++] = '0'; - prefix[prefix_size++] = specs.type; - } - int num_digits = count_digits<4>(abs_value); - writer.write_int(num_digits, get_prefix(), specs, - hex_writer{*this, num_digits}); + int num_digits = count_digits<4>(abs_value); + out = write_int(out, num_digits, get_prefix(), specs, + [this, num_digits](iterator it) { + return format_uint<4, Char>(it, abs_value, num_digits, + specs.type != 'x'); + }); + } + + void on_bin() { + if (specs.alt) { + prefix[prefix_size++] = '0'; + prefix[prefix_size++] = static_cast(specs.type); } - - template struct bin_writer { - unsigned_type abs_value; - int num_digits; - - template void operator()(It&& it) const { - it = format_uint(it, abs_value, num_digits); - } - }; - - void on_bin() { - if (specs.alt) { - prefix[prefix_size++] = '0'; - prefix[prefix_size++] = static_cast(specs.type); - } - int num_digits = count_digits<1>(abs_value); - writer.write_int(num_digits, get_prefix(), specs, - bin_writer<1>{abs_value, num_digits}); + int num_digits = count_digits<1>(abs_value); + out = write_int(out, num_digits, get_prefix(), specs, + [this, num_digits](iterator it) { + return format_uint<1, Char>(it, abs_value, num_digits); + }); + } + + void on_oct() { + int num_digits = count_digits<3>(abs_value); + if (specs.alt && specs.precision <= num_digits && abs_value != 0) { + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + prefix[prefix_size++] = '0'; } + out = write_int(out, num_digits, get_prefix(), specs, + [this, num_digits](iterator it) { + return format_uint<3, Char>(it, abs_value, num_digits); + }); + } - void on_oct() { - int num_digits = count_digits<3>(abs_value); - if (specs.alt && specs.precision <= num_digits && abs_value != 0) { - // Octal prefix '0' is counted as a digit, so only add it if precision - // is not greater than the number of digits. - prefix[prefix_size++] = '0'; - } - writer.write_int(num_digits, get_prefix(), specs, - bin_writer<3>{abs_value, num_digits}); - } + enum { sep_size = 1 }; - enum { sep_size = 1 }; - - struct num_writer { - unsigned_type abs_value; - int size; - const std::string& groups; - char_type sep; - - template void operator()(It&& it) const { - basic_string_view s(&sep, sep_size); - // Index of a decimal digit with the least significant digit having - // index 0. - int digit_index = 0; - std::string::const_iterator group = groups.cbegin(); - it = format_decimal( - it, abs_value, size, - [this, s, &group, &digit_index](char_type*& buffer) { - if (*group <= 0 || ++digit_index % *group != 0 || - *group == max_value()) - return; - if (group + 1 != groups.cend()) { - digit_index = 0; - ++group; - } - buffer -= s.size(); - std::uninitialized_copy(s.data(), s.data() + s.size(), - make_checked(buffer, s.size())); - }); - } - }; - - void on_num() { - std::string groups = grouping(writer.locale_); - if (groups.empty()) return on_dec(); - auto sep = thousands_sep(writer.locale_); - if (!sep) return on_dec(); - int num_digits = count_digits(abs_value); - int size = num_digits; - std::string::const_iterator group = groups.cbegin(); - while (group != groups.cend() && num_digits > *group && *group > 0 && - *group != max_value()) { - size += sep_size; - num_digits -= *group; + void on_num() { + std::string groups = grouping(locale); + if (groups.empty()) return on_dec(); + auto sep = thousands_sep(locale); + if (!sep) return on_dec(); + int num_digits = count_digits(abs_value); + int size = num_digits, n = num_digits; + std::string::const_iterator group = groups.cbegin(); + while (group != groups.cend() && num_digits > *group && *group > 0 && + *group != max_value()) { + size += sep_size; + n -= *group; + ++group; + } + if (group == groups.cend()) size += sep_size * ((n - 1) / groups.back()); + char digits[40]; + format_decimal(digits, abs_value, num_digits); + basic_memory_buffer buffer; + size += prefix_size; + buffer.resize(size); + basic_string_view s(&sep, sep_size); + // Index of a decimal digit with the least significant digit having index 0. + int digit_index = 0; + group = groups.cbegin(); + auto p = buffer.data() + size; + for (int i = num_digits - 1; i >= 0; --i) { + *--p = static_cast(digits[i]); + if (*group <= 0 || ++digit_index % *group != 0 || + *group == max_value()) + continue; + if (group + 1 != groups.cend()) { + digit_index = 0; ++group; } - if (group == groups.cend()) - size += sep_size * ((num_digits - 1) / groups.back()); - writer.write_int(size, get_prefix(), specs, - num_writer{abs_value, size, groups, sep}); - } - - FMT_NORETURN void on_error() { - FMT_THROW(format_error("invalid type specifier")); + p -= s.size(); + std::uninitialized_copy(s.data(), s.data() + s.size(), + make_checked(p, s.size())); } - }; + if (prefix_size != 0) p[-1] = static_cast('-'); + write(out, basic_string_view(buffer.data(), buffer.size()), specs); + } - template struct str_writer { - const Char* s; - size_t size_; + void on_chr() { *out++ = static_cast(abs_value); } - size_t size() const { return size_; } - size_t width() const { - return count_code_points(basic_string_view(s, size_)); - } + FMT_NORETURN void on_error() { + FMT_THROW(format_error("invalid type specifier")); + } +}; - template void operator()(It&& it) const { - it = copy_str(s, s + size_, it); - } +template +OutputIt write_nonfinite(OutputIt out, bool isinf, + const basic_format_specs& specs, + const float_specs& fspecs) { + auto str = + isinf ? (fspecs.upper ? "INF" : "inf") : (fspecs.upper ? "NAN" : "nan"); + constexpr size_t str_size = 3; + auto sign = fspecs.sign; + auto size = str_size + (sign ? 1 : 0); + using iterator = remove_reference_t; + return write_padded(out, specs, size, [=](iterator it) { + if (sign) *it++ = static_cast(data::signs[sign]); + return copy_str(str, str + str_size, it); + }); +} + +template ::value)> +OutputIt write(OutputIt out, T value, basic_format_specs specs, + locale_ref loc = {}) { + if (const_check(!is_supported_floating_point(value))) return out; + float_specs fspecs = parse_float_type_spec(specs); + fspecs.sign = specs.sign; + if (std::signbit(value)) { // value < 0 is false for NaN so use signbit. + fspecs.sign = sign::minus; + value = -value; + } else if (fspecs.sign == sign::minus) { + fspecs.sign = sign::none; + } + + if (!std::isfinite(value)) + return write_nonfinite(out, std::isinf(value), specs, fspecs); + + if (specs.align == align::numeric && fspecs.sign) { + auto it = reserve(out, 1); + *it++ = static_cast(data::signs[fspecs.sign]); + out = base_iterator(out, it); + fspecs.sign = sign::none; + if (specs.width != 0) --specs.width; + } + + memory_buffer buffer; + if (fspecs.format == float_format::hex) { + if (fspecs.sign) buffer.push_back(data::signs[fspecs.sign]); + snprintf_float(promote_float(value), specs.precision, fspecs, buffer); + return write_bytes(out, {buffer.data(), buffer.size()}, specs); + } + int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6; + if (fspecs.format == float_format::exp) { + if (precision == max_value()) + FMT_THROW(format_error("number is too big")); + else + ++precision; + } + if (const_check(std::is_same())) fspecs.binary32 = true; + fspecs.use_grisu = use_grisu(); + int exp = format_float(promote_float(value), precision, fspecs, buffer); + fspecs.precision = precision; + Char point = + fspecs.locale ? decimal_point(loc) : static_cast('.'); + float_writer w(buffer.data(), static_cast(buffer.size()), exp, + fspecs, point); + return write_padded(out, specs, w.size(), w); +} + +template ::value)> +OutputIt write(OutputIt out, T value) { + if (const_check(!is_supported_floating_point(value))) return out; + auto fspecs = float_specs(); + if (std::signbit(value)) { // value < 0 is false for NaN so use signbit. + fspecs.sign = sign::minus; + value = -value; + } + + auto specs = basic_format_specs(); + if (!std::isfinite(value)) + return write_nonfinite(out, std::isinf(value), specs, fspecs); + + memory_buffer buffer; + int precision = -1; + if (const_check(std::is_same())) fspecs.binary32 = true; + fspecs.use_grisu = use_grisu(); + int exp = format_float(promote_float(value), precision, fspecs, buffer); + fspecs.precision = precision; + float_writer w(buffer.data(), static_cast(buffer.size()), exp, + fspecs, static_cast('.')); + return base_iterator(out, w(reserve(out, w.size()))); +} + +template +OutputIt write_char(OutputIt out, Char value, + const basic_format_specs& specs) { + using iterator = remove_reference_t; + return write_padded(out, specs, 1, [=](iterator it) { + *it++ = value; + return it; + }); +} + +template +OutputIt write_ptr(OutputIt out, UIntPtr value, + const basic_format_specs* specs) { + int num_digits = count_digits<4>(value); + auto size = to_unsigned(num_digits) + size_t(2); + using iterator = remove_reference_t; + auto write = [=](iterator it) { + *it++ = static_cast('0'); + *it++ = static_cast('x'); + return format_uint<4, Char>(it, value, num_digits); }; + return specs ? write_padded(out, *specs, size, write) + : base_iterator(out, write(reserve(out, size))); +} - struct bytes_writer { - string_view bytes; +template struct is_integral : std::is_integral {}; +template <> struct is_integral : std::true_type {}; +template <> struct is_integral : std::true_type {}; - size_t size() const { return bytes.size(); } - size_t width() const { return bytes.size(); } +template +OutputIt write(OutputIt out, monostate) { + FMT_ASSERT(false, ""); + return out; +} - template void operator()(It&& it) const { - const char* data = bytes.data(); - it = copy_str(data, data + size(), it); - } - }; +template ::value)> +OutputIt write(OutputIt out, string_view value) { + auto it = reserve(out, value.size()); + it = copy_str(value.begin(), value.end(), it); + return base_iterator(out, it); +} - template struct pointer_writer { - UIntPtr value; - int num_digits; +template +OutputIt write(OutputIt out, basic_string_view value) { + auto it = reserve(out, value.size()); + it = std::copy(value.begin(), value.end(), it); + return base_iterator(out, it); +} - size_t size() const { return to_unsigned(num_digits) + 2; } - size_t width() const { return size(); } +template ::value && + !std::is_same::value && + !std::is_same::value)> +OutputIt write(OutputIt out, T value) { + auto abs_value = static_cast>(value); + bool negative = is_negative(value); + // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer. + if (negative) abs_value = ~abs_value + 1; + int num_digits = count_digits(abs_value); + auto it = reserve(out, (negative ? 1 : 0) + static_cast(num_digits)); + if (negative) *it++ = static_cast('-'); + it = format_decimal(it, abs_value, num_digits).end; + return base_iterator(out, it); +} - template void operator()(It&& it) const { - *it++ = static_cast('0'); - *it++ = static_cast('x'); - it = format_uint<4, char_type>(it, value, num_digits); - } - }; +template +OutputIt write(OutputIt out, bool value) { + return write(out, string_view(value ? "true" : "false")); +} - public: - explicit basic_writer(Range out, locale_ref loc = locale_ref()) - : out_(out.begin()), locale_(loc) {} - - iterator out() const { return out_; } - - // Writes a value in the format - // - // where is written by f(it). - template void write_padded(const format_specs& specs, F&& f) { - // User-perceived width (in code points). - unsigned width = to_unsigned(specs.width); - size_t size = f.size(); // The number of code units. - size_t num_code_points = width != 0 ? f.width() : size; - if (width <= num_code_points) return f(reserve(size)); - size_t padding = width - num_code_points; - size_t fill_size = specs.fill.size(); - auto&& it = reserve(size + padding * fill_size); - if (specs.align == align::right) { - it = fill(it, padding, specs.fill); - f(it); - } else if (specs.align == align::center) { - std::size_t left_padding = padding / 2; - it = fill(it, left_padding, specs.fill); - f(it); - it = fill(it, padding - left_padding, specs.fill); - } else { - f(it); - it = fill(it, padding, specs.fill); - } +template +OutputIt write(OutputIt out, Char value) { + auto it = reserve(out, 1); + *it++ = value; + return base_iterator(out, it); +} + +template +OutputIt write(OutputIt out, const Char* value) { + if (!value) { + FMT_THROW(format_error("string pointer is null")); + } else { + auto length = std::char_traits::length(value); + out = write(out, basic_string_view(value, length)); } + return out; +} + +template +OutputIt write(OutputIt out, const void* value) { + return write_ptr(out, to_uintptr(value), nullptr); +} - void write(int value) { write_decimal(value); } - void write(long value) { write_decimal(value); } - void write(long long value) { write_decimal(value); } +template +auto write(OutputIt out, const T& value) -> typename std::enable_if< + mapped_type_constant>::value == + type::custom_type, + OutputIt>::type { + basic_format_context ctx(out, {}, {}); + return formatter().format(value, ctx); +} - void write(unsigned value) { write_decimal(value); } - void write(unsigned long value) { write_decimal(value); } - void write(unsigned long long value) { write_decimal(value); } +// An argument visitor that formats the argument and writes it via the output +// iterator. It's a class and not a generic lambda for compatibility with C++11. +template struct default_arg_formatter { + using context = basic_format_context; -#if FMT_USE_INT128 - void write(int128_t value) { write_decimal(value); } - void write(uint128_t value) { write_decimal(value); } -#endif + OutputIt out; + basic_format_args args; + locale_ref loc; - template - void write_int(T value, const Spec& spec) { - handle_int_type_spec(spec.type, int_writer(*this, value, spec)); + template OutputIt operator()(T value) { + return write(out, value); } - template ::value)> - void write(T value, format_specs specs = {}) { - if (const_check(!is_supported_floating_point(value))) { - return; - } - float_specs fspecs = parse_float_type_spec(specs); - fspecs.sign = specs.sign; - if (std::signbit(value)) { // value < 0 is false for NaN so use signbit. - fspecs.sign = sign::minus; - value = -value; - } else if (fspecs.sign == sign::minus) { - fspecs.sign = sign::none; - } + OutputIt operator()(typename basic_format_arg::handle handle) { + basic_format_parse_context parse_ctx({}); + basic_format_context format_ctx(out, args, loc); + handle.format(parse_ctx, format_ctx); + return format_ctx.out(); + } +}; - if (!std::isfinite(value)) { - auto str = std::isinf(value) ? (fspecs.upper ? "INF" : "inf") - : (fspecs.upper ? "NAN" : "nan"); - return write_padded(specs, nonfinite_writer{fspecs.sign, str}); - } +template +class arg_formatter_base { + public: + using iterator = OutputIt; + using char_type = Char; + using format_specs = basic_format_specs; - if (specs.align == align::none) { - specs.align = align::right; - } else if (specs.align == align::numeric) { - if (fspecs.sign) { - auto&& it = reserve(1); - *it++ = static_cast(data::signs[fspecs.sign]); - fspecs.sign = sign::none; - if (specs.width != 0) --specs.width; - } - specs.align = align::right; - } + private: + iterator out_; + locale_ref locale_; + format_specs* specs_; - memory_buffer buffer; - if (fspecs.format == float_format::hex) { - if (fspecs.sign) buffer.push_back(data::signs[fspecs.sign]); - snprintf_float(promote_float(value), specs.precision, fspecs, buffer); - write_padded(specs, str_writer{buffer.data(), buffer.size()}); - return; - } - int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6; - if (fspecs.format == float_format::exp) { - if (precision == max_value()) - FMT_THROW(format_error("number is too big")); - else - ++precision; - } - if (const_check(std::is_same())) fspecs.binary32 = true; - fspecs.use_grisu = use_grisu(); - if (const_check(FMT_DEPRECATED_PERCENT) && fspecs.percent) value *= 100; - int exp = format_float(promote_float(value), precision, fspecs, buffer); - if (const_check(FMT_DEPRECATED_PERCENT) && fspecs.percent) { - buffer.push_back('%'); - --exp; // Adjust decimal place position. - } - fspecs.precision = precision; - char_type point = fspecs.locale ? decimal_point(locale_) - : static_cast('.'); - write_padded(specs, float_writer(buffer.data(), - static_cast(buffer.size()), - exp, fspecs, point)); + // Attempts to reserve space for n extra characters in the output range. + // Returns a pointer to the reserved range or a reference to out_. + auto reserve(size_t n) -> decltype(detail::reserve(out_, n)) { + return detail::reserve(out_, n); + } + + using reserve_iterator = remove_reference_t(), 0))>; + + template void write_int(T value, const format_specs& spec) { + using uint_type = uint32_or_64_or_128_t; + int_writer w(out_, locale_, value, spec); + handle_int_type_spec(spec.type, w); + out_ = w.out; } void write(char value) { @@ -1782,198 +1848,151 @@ template class basic_writer { *it++ = value; } - template ::value)> - void write(Char value) { - auto&& it = reserve(1); - *it++ = value; + template ::value)> + void write(Ch value) { + out_ = detail::write(out_, value); } void write(string_view value) { auto&& it = reserve(value.size()); - it = copy_str(value.begin(), value.end(), it); + it = copy_str(value.begin(), value.end(), it); } void write(wstring_view value) { - static_assert(std::is_same::value, ""); + static_assert(std::is_same::value, ""); auto&& it = reserve(value.size()); it = std::copy(value.begin(), value.end(), it); } - template - void write(const Char* s, std::size_t size, const format_specs& specs) { - write_padded(specs, str_writer{s, size}); + template + void write(const Ch* s, size_t size, const format_specs& specs) { + auto width = specs.width != 0 + ? count_code_points(basic_string_view(s, size)) + : 0; + out_ = write_padded(out_, specs, size, width, [=](reserve_iterator it) { + return copy_str(s, s + size, it); + }); } - template - void write(basic_string_view s, const format_specs& specs = {}) { - const Char* data = s.data(); - std::size_t size = s.size(); - if (specs.precision >= 0 && to_unsigned(specs.precision) < size) - size = code_point_index(s, to_unsigned(specs.precision)); - write(data, size, specs); + template + void write(basic_string_view s, const format_specs& specs = {}) { + out_ = detail::write(out_, s, specs); } - void write_bytes(string_view bytes, const format_specs& specs) { - write_padded(specs, bytes_writer{bytes}); - } - - template - void write_pointer(UIntPtr value, const format_specs* specs) { - int num_digits = count_digits<4>(value); - auto pw = pointer_writer{value, num_digits}; - if (!specs) return pw(reserve(to_unsigned(num_digits) + 2)); - format_specs specs_copy = *specs; - if (specs_copy.align == align::none) specs_copy.align = align::right; - write_padded(specs_copy, pw); + void write_pointer(const void* p) { + out_ = write_ptr(out_, to_uintptr(p), specs_); } -}; -using writer = basic_writer>; - -template struct is_integral : std::is_integral {}; -template <> struct is_integral : std::true_type {}; -template <> struct is_integral : std::true_type {}; + struct char_spec_handler : ErrorHandler { + arg_formatter_base& formatter; + Char value; -template -class arg_formatter_base { - public: - using char_type = typename Range::value_type; - using iterator = typename Range::iterator; - using format_specs = basic_format_specs; + char_spec_handler(arg_formatter_base& f, Char val) + : formatter(f), value(val) {} - private: - using writer_type = basic_writer; - writer_type writer_; - format_specs* specs_; + void on_int() { + // char is only formatted as int if there are specs. + formatter.write_int(static_cast(value), *formatter.specs_); + } + void on_char() { + if (formatter.specs_) + formatter.out_ = write_char(formatter.out_, value, *formatter.specs_); + else + formatter.write(value); + } + }; - struct char_writer { - char_type value; + struct cstring_spec_handler : error_handler { + arg_formatter_base& formatter; + const Char* value; - size_t size() const { return 1; } - size_t width() const { return 1; } + cstring_spec_handler(arg_formatter_base& f, const Char* val) + : formatter(f), value(val) {} - template void operator()(It&& it) const { *it++ = value; } + void on_string() { formatter.write(value); } + void on_pointer() { formatter.write_pointer(value); } }; - void write_char(char_type value) { - if (specs_) - writer_.write_padded(*specs_, char_writer{value}); - else - writer_.write(value); - } - - void write_pointer(const void* p) { - writer_.write_pointer(internal::to_uintptr(p), specs_); - } - protected: - writer_type& writer() { return writer_; } - FMT_DEPRECATED format_specs* spec() { return specs_; } + iterator out() { return out_; } format_specs* specs() { return specs_; } - iterator out() { return writer_.out(); } void write(bool value) { - string_view sv(value ? "true" : "false"); - specs_ ? writer_.write(sv, *specs_) : writer_.write(sv); + if (specs_) + write(string_view(value ? "true" : "false"), *specs_); + else + out_ = detail::write(out_, value); } - void write(const char_type* value) { + void write(const Char* value) { if (!value) { FMT_THROW(format_error("string pointer is null")); } else { auto length = std::char_traits::length(value); basic_string_view sv(value, length); - specs_ ? writer_.write(sv, *specs_) : writer_.write(sv); + specs_ ? write(sv, *specs_) : write(sv); } } public: - arg_formatter_base(Range r, format_specs* s, locale_ref loc) - : writer_(r, loc), specs_(s) {} + arg_formatter_base(OutputIt out, format_specs* s, locale_ref loc) + : out_(out), locale_(loc), specs_(s) {} iterator operator()(monostate) { FMT_ASSERT(false, "invalid argument type"); - return out(); + return out_; } template ::value)> - iterator operator()(T value) { + FMT_INLINE iterator operator()(T value) { if (specs_) - writer_.write_int(value, *specs_); + write_int(value, *specs_); else - writer_.write(value); - return out(); + out_ = detail::write(out_, value); + return out_; } - iterator operator()(char_type value) { - internal::handle_char_specs( - specs_, char_spec_handler(*this, static_cast(value))); - return out(); + iterator operator()(Char value) { + handle_char_specs(specs_, + char_spec_handler(*this, static_cast(value))); + return out_; } iterator operator()(bool value) { if (specs_ && specs_->type) return (*this)(value ? 1 : 0); write(value != 0); - return out(); + return out_; } template ::value)> iterator operator()(T value) { + auto specs = specs_ ? *specs_ : format_specs(); if (const_check(is_supported_floating_point(value))) - writer_.write(value, specs_ ? *specs_ : format_specs()); + out_ = detail::write(out_, value, specs, locale_); else FMT_ASSERT(false, "unsupported float argument type"); - return out(); + return out_; } - struct char_spec_handler : ErrorHandler { - arg_formatter_base& formatter; - char_type value; - - char_spec_handler(arg_formatter_base& f, char_type val) - : formatter(f), value(val) {} - - void on_int() { - if (formatter.specs_) - formatter.writer_.write_int(value, *formatter.specs_); - else - formatter.writer_.write(value); - } - void on_char() { formatter.write_char(value); } - }; - - struct cstring_spec_handler : internal::error_handler { - arg_formatter_base& formatter; - const char_type* value; - - cstring_spec_handler(arg_formatter_base& f, const char_type* val) - : formatter(f), value(val) {} - - void on_string() { formatter.write(value); } - void on_pointer() { formatter.write_pointer(value); } - }; - - iterator operator()(const char_type* value) { - if (!specs_) return write(value), out(); - internal::handle_cstring_type_spec(specs_->type, - cstring_spec_handler(*this, value)); - return out(); + iterator operator()(const Char* value) { + if (!specs_) return write(value), out_; + handle_cstring_type_spec(specs_->type, cstring_spec_handler(*this, value)); + return out_; } - iterator operator()(basic_string_view value) { + iterator operator()(basic_string_view value) { if (specs_) { - internal::check_string_type_spec(specs_->type, internal::error_handler()); - writer_.write(value, *specs_); + check_string_type_spec(specs_->type, error_handler()); + write(value, *specs_); } else { - writer_.write(value); + write(value); } - return out(); + return out_; } iterator operator()(const void* value) { - if (specs_) - check_pointer_type_spec(specs_->type, internal::error_handler()); + if (specs_) check_pointer_type_spec(specs_->type, error_handler()); write_pointer(value); - return out(); + return out_; } }; @@ -2109,7 +2128,7 @@ template class specs_setter { template class numeric_specs_checker { public: - FMT_CONSTEXPR numeric_specs_checker(ErrorHandler& eh, internal::type arg_type) + FMT_CONSTEXPR numeric_specs_checker(ErrorHandler& eh, detail::type arg_type) : error_handler_(eh), arg_type_(arg_type) {} FMT_CONSTEXPR void require_numeric_argument() { @@ -2132,18 +2151,24 @@ template class numeric_specs_checker { private: ErrorHandler& error_handler_; - internal::type arg_type_; + detail::type arg_type_; }; // A format specifier handler that checks if specifiers are consistent with the // argument type. template class specs_checker : public Handler { + private: + numeric_specs_checker checker_; + + // Suppress an MSVC warning about using this in initializer list. + FMT_CONSTEXPR Handler& error_handler() { return *this; } + public: - FMT_CONSTEXPR specs_checker(const Handler& handler, internal::type arg_type) - : Handler(handler), checker_(*this, arg_type) {} + FMT_CONSTEXPR specs_checker(const Handler& handler, detail::type arg_type) + : Handler(handler), checker_(error_handler(), arg_type) {} FMT_CONSTEXPR specs_checker(const specs_checker& other) - : Handler(other), checker_(*this, other.arg_type_) {} + : Handler(other), checker_(error_handler(), other.arg_type_) {} FMT_CONSTEXPR void on_align(align_t align) { if (align == align::numeric) checker_.require_numeric_argument(); @@ -2176,9 +2201,6 @@ template class specs_checker : public Handler { } FMT_CONSTEXPR void end_precision() { checker_.check_precision(); } - - private: - numeric_specs_checker checker_; }; template