strformat: support 'sign' as Python does

This commit is contained in:
Araq 2017-12-17 17:58:04 +01:00
commit 2e61e6edf9

View file

@ -92,7 +92,7 @@ Standard format specifier
The general form of a standard format specifier is:: The general form of a standard format specifier is::
[[fill]align][#][0][minimumwidth][.precision][type] [[fill]align][sign][#][0][minimumwidth][.precision][type]
The brackets ([]) indicate an optional element. The brackets ([]) indicate an optional element.
@ -116,6 +116,18 @@ The optional 'fill' character defines the character to be used to pad
the field to the minimum width. The fill character, if present, must be the field to the minimum width. The fill character, if present, must be
followed by an alignment flag. followed by an alignment flag.
The 'sign' option is only valid for numeric types, and can be one of the following:
================= ====================================================
Sign Meaning
================= ====================================================
``+`` Indicates that a sign should be used for both
positive as well as negative numbers.
``-`` Indicates that a sign should be used only for
negative numbers (this is the default behavior).
`` `` (space) Indicates that a leading space should be used on
positive numbers.
If the '#' character is present, integers use the 'alternate form' for formatting. If the '#' character is present, integers use the 'alternate form' for formatting.
This means that binary, octal, and hexadecimal output will be prefixed This means that binary, octal, and hexadecimal output will be prefixed
with '0b', '0o', and '0x', respectively. with '0b', '0o', and '0x', respectively.
@ -273,6 +285,7 @@ macro fmt*(pattern: string): untyped =
check fmt"{10}", "10" check fmt"{10}", "10"
check fmt"{16:#X}", "0x10" check fmt"{16:#X}", "0x10"
check fmt"{16:^#7X}", " 0x10 " check fmt"{16:^#7X}", " 0x10 "
check fmt"{16:^+#7X}", " +0x10 "
# Hex tests # Hex tests
check fmt"{0:x}", "0" check fmt"{0:x}", "0"
@ -292,6 +305,8 @@ macro fmt*(pattern: string): untyped =
check fmt"{123.456}", "123.456" check fmt"{123.456}", "123.456"
check fmt"{-123.456}", "-123.456" check fmt"{-123.456}", "-123.456"
check fmt"{123.456:.3f}", "123.456" check fmt"{123.456:.3f}", "123.456"
check fmt"{123.456:+.3f}", "+123.456"
check fmt"{-123.456:+.3f}", "-123.456"
check fmt"{-123.456:.3f}", "-123.456" check fmt"{-123.456:.3f}", "-123.456"
check fmt"{123.456:1g}", "123.456" check fmt"{123.456:1g}", "123.456"
check fmt"{123.456:.1f}", "123.5" check fmt"{123.456:.1f}", "123.5"
@ -417,8 +432,7 @@ proc alignString*(s: string, minimumWidth: int; align = '<'; fill = ' '): string
type type
StandardFormatSpecifier* = object ## Type that describes "standard format specifiers". StandardFormatSpecifier* = object ## Type that describes "standard format specifiers".
fill*, align*: char ## Desired fill and alignment. fill*, align*: char ## Desired fill and alignment.
when false: sign*: char ## Desired sign.
sign: char ## Desired sign.
alternateForm*: bool ## Whether to prefix binary, octal and hex numbers alternateForm*: bool ## Whether to prefix binary, octal and hex numbers
## with ``0b``, ``0o``, ``0x``. ## with ``0b``, ``0o``, ``0x``.
padWithZero*: bool ## Whether to pad with zeros rather than spaces. padWithZero*: bool ## Whether to pad with zeros rather than spaces.
@ -461,24 +475,23 @@ proc formatInt(n: SomeNumber; radix: int; spec: StandardFormatSpecifier): string
result.add(mkDigit(d.int, spec.typ)) result.add(mkDigit(d.int, spec.typ))
for idx in 0..<(result.len div 2): for idx in 0..<(result.len div 2):
swap result[idx], result[result.len - idx - 1] swap result[idx], result[result.len - idx - 1]
let adjustedWid = if negative: spec.minimumWidth - 1 else: spec.minimumWidth
if spec.padWithZero: if spec.padWithZero:
let toFill = spec.minimumWidth - result.len - xx.len - ord(negative) let sign = negative or spec.sign != '-'
let toFill = spec.minimumWidth - result.len - xx.len - ord(sign)
if toFill > 0: if toFill > 0:
result = repeat('0', toFill) & result result = repeat('0', toFill) & result
if negative:
result = "-" & xx & result
elif spec.sign != '-':
result = spec.sign & xx & result
else:
result = xx & result
if spec.align == '<': if spec.align == '<':
if negative:
result = "-" & xx & result
else:
result = xx & result
for i in result.len..<spec.minimumWidth: for i in result.len..<spec.minimumWidth:
result.add(spec.fill) result.add(spec.fill)
else: else:
if negative:
result = "-" & xx & result
else:
result = xx & result
let toFill = spec.minimumWidth - result.len let toFill = spec.minimumWidth - result.len
if spec.align == '^': if spec.align == '^':
let half = toFill div 2 let half = toFill div 2
@ -492,7 +505,7 @@ proc parseStandardFormatSpecifier*(s: string; start = 0;
## An exported helper proc that parses the "standard format specifiers", ## An exported helper proc that parses the "standard format specifiers",
## as specified by the grammar:: ## as specified by the grammar::
## ##
## [[fill]align][#][0][minimumwidth][.precision][type] ## [[fill]align][sign][#][0][minimumwidth][.precision][type]
## ##
## This is only of interest if you want to write a custom ``format`` proc that ## This is only of interest if you want to write a custom ``format`` proc that
## should support the standard format specifiers. If ``ignoreUnknownSuffix`` is true, ## should support the standard format specifiers. If ``ignoreUnknownSuffix`` is true,
@ -500,6 +513,7 @@ proc parseStandardFormatSpecifier*(s: string; start = 0;
const alignChars = {'<', '>', '^'} const alignChars = {'<', '>', '^'}
result.fill = ' ' result.fill = ' '
result.align = '<' result.align = '<'
result.sign = '-'
var i = start var i = start
if i + 1 < s.len and s[i+1] in alignChars: if i + 1 < s.len and s[i+1] in alignChars:
result.fill = s[i] result.fill = s[i]
@ -509,11 +523,9 @@ proc parseStandardFormatSpecifier*(s: string; start = 0;
result.align = s[i] result.align = s[i]
inc i inc i
when false: if i < s.len and s[i] in {'-', '+', ' '}:
# XXX Python inspired 'sign' not yet supported! result.sign = s[i]
if i < s.len and s[i] in {'-', '+', ' '}: inc i
result.sign = s[i]
inc i
if i < s.len and s[i] == '#': if i < s.len and s[i] == '#':
result.alternateForm = true result.alternateForm = true
@ -578,12 +590,11 @@ proc format*(value: SomeReal; specifier: string; res: var string) =
"invalid type in format string for number, expected one " & "invalid type in format string for number, expected one " &
" of 'e', 'E', 'f', 'F', 'g', 'G' but got: " & spec.typ) " of 'e', 'E', 'f', 'F', 'g', 'G' but got: " & spec.typ)
#let result = if spec.minimumWidth > 0 and spec.align == '<' and value < 0 and spec.padWithZero: var f = formatBiggestFloat(value, fmode, spec.precision)
# "-" & alignString(formatBiggestFloat(-value, fmode, spec.precision), spec.minimumWidth-1, if value >= 0.0 and spec.sign != '-':
# spec.align, '0') f = spec.sign & f
#else: let result = alignString(f, spec.minimumWidth,
let result = alignString(formatBiggestFloat(value, fmode, spec.precision), spec.minimumWidth, spec.align, spec.fill)
spec.align, spec.fill)
if spec.typ in {'A'..'Z'}: if spec.typ in {'A'..'Z'}:
res.add toUpperAscii(result) res.add toUpperAscii(result)
else: else: