-
Notifications
You must be signed in to change notification settings - Fork 404
feat(ufmt): enhance printf functionnalities by adding flags #4136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
502c85b
f72fd4c
10c4960
30780d2
d935be7
966de11
4923018
80bf496
20260f7
5455127
5d71b3a
f396fe4
a91aac5
efb7a3e
c5d0400
9451a3b
8289230
f2359c8
ce15ebf
9755a09
6decc3f
6b914d3
d83de10
6ea1081
76d2680
67b2a21
d172d78
feaffc2
fd9e9fb
442a327
6c44073
2dc5f13
47b20da
a9633ce
53a20d1
b6c03bf
d79fd53
6353239
1d29c1c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,8 +8,10 @@ package ufmt | |
import ( | ||
"errors" | ||
"io" | ||
"math" | ||
"strconv" | ||
"strings" | ||
"unicode" | ||
"unicode/utf8" | ||
) | ||
|
||
|
@@ -32,13 +34,134 @@ func (b *buffer) writeRune(r rune) { | |
*b = utf8.AppendRune(*b, r) | ||
} | ||
|
||
// the different flags | ||
const ( | ||
flagHashtag uint8 = 1 | ||
flagZero uint8 = 1 << 1 | ||
flagMinus uint8 = 1 << 2 | ||
flagSpace uint8 = 1 << 3 | ||
flagPlus uint8 = 1 << 4 | ||
) | ||
|
||
// the different len modifiers | ||
const ( | ||
lenNo uint8 = iota | ||
lenHH | ||
lenH | ||
lenL | ||
lenLL | ||
lenZ | ||
) | ||
|
||
type printerMeta struct { | ||
flags uint8 | ||
padding uint | ||
length uint8 | ||
width uint | ||
precision int | ||
} | ||
|
||
// printer holds state for formatting operations. | ||
type printer struct { | ||
buf buffer | ||
buf buffer | ||
meta printerMeta | ||
} | ||
|
||
func strtoi(s string) (int, string) { | ||
i := 0 | ||
for i < len(s) && unicode.IsDigit(rune(s[i])) { | ||
i++ | ||
} | ||
if i == 0 { | ||
return 0, s | ||
} | ||
num, err := strconv.Atoi(s[:i]) | ||
if err != nil { | ||
return 0, s | ||
} | ||
return num, s[i:] | ||
} | ||
|
||
func ptrForward(ptr *int, runes []rune, inc int) { | ||
if inc <= 0 { | ||
return | ||
} | ||
if *ptr + inc > len(runes) { | ||
return | ||
} | ||
*ptr += inc | ||
} | ||
|
||
var c2f = map[rune]uint8{ | ||
'-': flagMinus, | ||
'+': flagPlus, | ||
' ': flagSpace, | ||
'0': flagZero, | ||
} | ||
|
||
var lenMap = map[string]uint8{ | ||
"ll": lenLL, | ||
"l": lenL, | ||
"h": lenH, | ||
"hh": lenHH, | ||
"z": lenZ, | ||
} | ||
|
||
// parse the flags | ||
func (p *printer) parse(runes []rune, index *int) { | ||
ptrForward(index, runes, 1) | ||
if runes[*index] == '#' { | ||
p.meta.flags |= flagHashtag | ||
ptrForward(index, runes, 1) | ||
} | ||
stop := false | ||
for { | ||
x, ok := c2f[runes[*index]] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hiding the reference inside a map complicates readability and performance. Why not use a direct switch case like this: switch *index {
case '+'
case '-':
...
} |
||
switch { | ||
case ok: | ||
p.meta.flags |= x | ||
ptrForward(index, runes, 1) | ||
case unicode.IsNumber(runes[*index]): | ||
strRunes := string(runes[*index:]) | ||
n, rest := strtoi(strRunes) | ||
if rest == strRunes { | ||
continue | ||
} | ||
p.meta.padding = uint(n) | ||
ptrForward(index, runes, len(strRunes) - len(rest)) | ||
case runes[*index] == '.': | ||
ptrForward(index, runes, 1) | ||
strRunes := string(runes[*index:]) | ||
n, rest := strtoi(strRunes) | ||
p.meta.precision = n // 0 by default, not an error | ||
if rest == strRunes { | ||
continue | ||
} | ||
ptrForward(index, runes, len(strRunes) - len(rest)) | ||
default: | ||
stop = true | ||
} | ||
if stop { | ||
break | ||
} | ||
} | ||
for k, v := range lenMap { | ||
if string(runes[*index:*index+len(k)-1]) == k { | ||
paulogarithm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
p.meta.length = v | ||
ptrForward(index, runes, len(k)) | ||
break | ||
} | ||
} | ||
*index-- | ||
paulogarithm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
func newPrinter() *printer { | ||
return &printer{} | ||
return &printer{ | ||
buf: buffer{}, | ||
meta: printerMeta{ | ||
precision: 6, | ||
}, | ||
} | ||
} | ||
|
||
// Sprint formats using the default formats for its operands and returns the resulting string. | ||
|
@@ -110,7 +233,7 @@ func (p *printer) doPrintln(a []any) { | |
// %G: Formats a float value with %G for large exponents, and %F with full precision for smaller numbers | ||
// %t: Formats a boolean value to "true" or "false". | ||
// %x: Formats an integer value as a hexadecimal string. | ||
// Currently supports only uint8, []uint8, [32]uint8. | ||
// Currently supports only int, uint, uint8, 16, 32, 64 and []uint8. | ||
// %c: Formats a rune value as a string. | ||
// Currently supports only rune, int. | ||
// %q: Formats a string value as a quoted string. | ||
|
@@ -150,6 +273,8 @@ func (p *printer) doPrintf(format string, args []any) { | |
continue | ||
} | ||
|
||
p.parse(sTor, &i) | ||
|
||
verb := sTor[i+1] | ||
if verb == '%' { | ||
p.buf.writeRune('%') | ||
|
@@ -170,14 +295,14 @@ func (p *printer) doPrintf(format string, args []any) { | |
writeString(p, verb, arg) | ||
case 'c': | ||
writeChar(p, verb, arg) | ||
case 'd': | ||
case 'd', 'i': | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
writeInt(p, verb, arg) | ||
case 'e', 'E', 'f', 'F', 'g', 'G': | ||
writeFloat(p, verb, arg) | ||
case 't': | ||
writeBool(p, verb, arg) | ||
case 'x': | ||
writeHex(p, verb, arg) | ||
case 'x', 'X': | ||
writeHex(p, verb, arg, verb == 'X') | ||
case 'q': | ||
writeQuotedString(p, verb, arg) | ||
case 'T': | ||
|
@@ -276,53 +401,69 @@ func writeChar(p *printer, verb rune, arg any) { | |
} | ||
} | ||
|
||
// writeInt handles %d formatting | ||
// writeInt handles %d & %i formatting | ||
func writeInt(p *printer, verb rune, arg any) { | ||
var subBuf string | ||
switch v := arg.(type) { | ||
case int: | ||
p.buf.writeString(strconv.Itoa(v)) | ||
subBuf = strconv.Itoa(v) | ||
case int8: | ||
p.buf.writeString(strconv.Itoa(int(v))) | ||
subBuf = strconv.Itoa(int(v)) | ||
case int16: | ||
p.buf.writeString(strconv.Itoa(int(v))) | ||
subBuf = strconv.Itoa(int(v)) | ||
case int32: | ||
p.buf.writeString(strconv.Itoa(int(v))) | ||
subBuf = strconv.Itoa(int(v)) | ||
case int64: | ||
p.buf.writeString(strconv.Itoa(int(v))) | ||
subBuf = strconv.Itoa(int(v)) | ||
case uint: | ||
p.buf.writeString(strconv.FormatUint(uint64(v), 10)) | ||
subBuf = strconv.FormatUint(uint64(v), 10) | ||
case uint8: | ||
p.buf.writeString(strconv.FormatUint(uint64(v), 10)) | ||
subBuf = strconv.FormatUint(uint64(v), 10) | ||
case uint16: | ||
p.buf.writeString(strconv.FormatUint(uint64(v), 10)) | ||
subBuf = strconv.FormatUint(uint64(v), 10) | ||
case uint32: | ||
p.buf.writeString(strconv.FormatUint(uint64(v), 10)) | ||
subBuf = strconv.FormatUint(uint64(v), 10) | ||
case uint64: | ||
p.buf.writeString(strconv.FormatUint(v, 10)) | ||
subBuf = strconv.FormatUint(v, 10) | ||
default: | ||
p.buf.writeString(fallback(verb, v)) | ||
subBuf = fallback(verb, v) | ||
} | ||
paddingCount := int(p.meta.padding) - len(subBuf) | ||
if paddingCount < 0 { | ||
paddingCount = 0 | ||
} | ||
if p.meta.flags&flagSpace != 0 { | ||
subBuf = strings.Repeat(" ", paddingCount) + subBuf | ||
} else if p.meta.flags&flagZero != 0 { | ||
subBuf = strings.Repeat("0", paddingCount) + subBuf | ||
} | ||
p.buf.writeString(subBuf) | ||
} | ||
|
||
// writeFloat handles floating-point formatting verbs | ||
func writeFloat(p *printer, verb rune, arg any) { | ||
var subBuf string | ||
bits := 64 | ||
n := 0. | ||
switch v := arg.(type) { | ||
case float64: | ||
switch verb { | ||
case 'e': | ||
p.buf.writeString(strconv.FormatFloat(v, 'e', -1, 64)) | ||
case 'E': | ||
p.buf.writeString(strconv.FormatFloat(v, 'E', -1, 64)) | ||
case 'f', 'F': | ||
p.buf.writeString(strconv.FormatFloat(v, 'f', 6, 64)) | ||
case 'g': | ||
p.buf.writeString(strconv.FormatFloat(v, 'g', -1, 64)) | ||
case 'G': | ||
p.buf.writeString(strconv.FormatFloat(v, 'G', -1, 64)) | ||
} | ||
bits = 64 | ||
n = v | ||
case float32: | ||
bits = 32 | ||
n = float64(v) | ||
default: | ||
p.buf.writeString(fallback(verb, v)) | ||
return | ||
} | ||
prec := -1 | ||
verbChar := byte(verb) | ||
if verbChar == 'f' || verbChar == 'F' { | ||
prec = p.meta.precision | ||
verbChar = 'f' | ||
} | ||
subBuf = strconv.FormatFloat(n, verbChar, prec, bits) | ||
p.buf.writeString(subBuf) | ||
} | ||
|
||
// writeBool handles %t formatting | ||
|
@@ -339,14 +480,46 @@ func writeBool(p *printer, verb rune, arg any) { | |
} | ||
} | ||
|
||
// writeHex handles %x formatting | ||
func writeHex(p *printer, verb rune, arg any) { | ||
// writeHex handles %x & %X formatting | ||
func writeHex(p *printer, verb rune, arg any, big bool) { | ||
var subBuf string | ||
switch v := arg.(type) { | ||
case int: | ||
subBuf += strconv.FormatUint(uint64(v), 16) | ||
case uint: | ||
subBuf += strconv.FormatUint(uint64(v), 16) | ||
case uint8: | ||
p.buf.writeString(strconv.FormatUint(uint64(v), 16)) | ||
subBuf += strconv.FormatUint(uint64(v), 16) | ||
case uint16: | ||
subBuf += strconv.FormatUint(uint64(v), 16) | ||
case uint32: | ||
subBuf += strconv.FormatUint(uint64(v), 16) | ||
case uint64: | ||
subBuf += strconv.FormatUint(v, 16) | ||
case []uint8: | ||
for _, v := range v { | ||
x := strconv.FormatUint(uint64(v), 16) | ||
if len(x) == 1 { | ||
x = "0" + x | ||
} | ||
subBuf += x | ||
} | ||
default: | ||
p.buf.writeString("(unhandled)") | ||
return | ||
} | ||
if p.meta.flags&flagSpace != 0 { | ||
subBuf = strings.Repeat(" ", int(math.Max(0, float64(int(p.meta.padding)-len(subBuf))))) + subBuf | ||
} else if p.meta.flags&flagZero != 0 { | ||
subBuf = strings.Repeat("0", int(math.Max(0, float64(int(p.meta.padding)-len(subBuf))))) + subBuf | ||
} | ||
if p.meta.flags&flagHashtag != 0 { | ||
subBuf = "0x" + subBuf | ||
} | ||
if big { | ||
subBuf = strings.ToUpper(subBuf) | ||
} | ||
p.buf.writeString(subBuf) | ||
} | ||
|
||
// writeQuotedString handles %q formatting | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.