-
Notifications
You must be signed in to change notification settings - Fork 417
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?
Conversation
* includes gnominers * includes home * include pure packages
* reorganise gnominers into submodules * create the dao submodule * add the tofix folder with all broken features * create the minerdao /!\ DAO DOESNT WORK RN BECAUSE OF CYCLE DEPENDANCIES /!\
* i didnt use subfolders
* add precisision flag ("%.2f") * add 0 & ' ' flags ("%03d", "% 3d") * add length parsing (not used rn) * add # for hex ("%#02x") * add %i & %X (%i is just %d, %X is big hex) * change the home * ass some tests to handle what ive done
* also changed my home page so it displays a number
Format -> FormatRound because it rounds...
* cause of major issue because it was reading to far
* better messages * better formatting * use `p/moun/md` for md formatting in users.gno
tell me if i need to add anything else ! |
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.
I've confirmed that all the requested changes have been reflected and all tests have passed.
It's a minor point related to policy, but the "resolve conversation" button should not be pressed until the reviewer has checked it. Please refer to this document for more details.
Anyway, except for this, everything else looks good.
remove: review/triage-pending
flag
Hmm... When using I'll check this problem today. If it's okay, may I modify this code together with you? |
totally fine for me, @notJoon ! but maybe it's because of the maps, so when you'll test, can you try putting the maps in the scope like i did, even if it's more greedy on memory ? |
I'm testing by directly checking the gas consumption with txtar. It seems rather than map usage, we also need to improve parts related to memory allocation within the |
@paulogarithm I couldn't push directly to this PR #4174, so I created a separate PR. Please check it and feel free to cherry-pick if needed. thank you 👍 |
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.
Nice ! this is something really welcome !
Could you please update documentation above Sprintf
as well as indicating all currently supported flags ? (you can take inspiration from https://pkg.go.dev/fmt)
While we are here we could support padding as well ? (could be done in another PR)
I've tried your code against some go/fmt
test code, and the output differs in many formatting cases.
// advanced int (from go/fmt)
{"%.d", []any{0}, ""},
{"%.0d", []any{0}, ""},
{"% d", []any{12345}, " 12345"},
{"%+d", []any{12345}, "+12345"},
{"%+d", []any{-12345}, "-12345"},
{"%#X", []any{0}, "0X0"},
{"%x", []any{0x12abcdef}, "12abcdef"},
{"%X", []any{0x12abcdef}, "12ABCDEF"},
{"%x", []any{^uint32(0)}, "ffffffff"},
{"%X", []any{^uint64(0)}, "FFFFFFFFFFFFFFFF"},
// {"%010d", []any{12345}, "0000012345"},
// {"%010d", []any{-12345}, "-000012345"}, // weird behaviors
// advanced float (from go/fmt)
{"%+.3e", []any{0.0}, "+0.000e+00"},
{"%+.3e", []any{1.0}, "+1.000e+00"},
{"%+.3x", []any{0.0}, "+0x0.000p+00"},
{"%+.3x", []any{1.0}, "+0x1.000p+00"},
{"%+.3f", []any{-1.0}, "-1.000"},
{"%+.3F", []any{-1.0}, "-1.000"},
{"%+.3F", []any{float32(-1.0)}, "-1.000"},
{"% .3E", []any{-1.0}, "-1.000E+00"},
{"% .3e", []any{1.0}, " 1.000e+00"},
{"% .3X", []any{-1.0}, "-0X1.000P+00"},
{"% .3x", []any{1.0}, " 0x1.000p+00"},
{"%+.3g", []any{0.0}, "+0"},
{"%+.3g", []any{1.0}, "+1"},
{"%+.3g", []any{-1.0}, "-1"},
{"% .3g", []any{-1.0}, "-1"},
{"% .3g", []any{1.0}, " 1"},
// Test sharp flag used with floats.
{"%#g", []any{1e-323}, "1.00000e-323"},
{"%#g", []any{-1.0}, "-1.00000"},
{"%#g", []any{1.1}, "1.10000"},
{"%#g", []any{123456.0}, "123456."},
{"%#g", []any{1234567.0}, "1.234567e+06"},
{"%#g", []any{1230000.0}, "1.23000e+06"},
{"%#g", []any{1000000.0}, "1.00000e+06"},
{"%#.0f", []any{1.0}, "1."},
{"%#.0e", []any{1.0}, "1.e+00"},
{"%#.0x", []any{1.0}, "0x1.p+00"},
{"%#.0g", []any{1.0}, "1."},
{"%#.0g", []any{1100000.0}, "1.e+06"},
{"%#.4f", []any{1.0}, "1.0000"},
{"%#.4e", []any{1.0}, "1.0000e+00"},
{"%#.4x", []any{1.0}, "0x1.0000p+00"},
{"%#.4g", []any{1.0}, "1.000"},
{"%#.4g", []any{100000.0}, "1.000e+05"},
{"%#.4g", []any{1.234}, "1.234"},
{"%#.4g", []any{0.1234}, "0.1234"},
{"%#.4g", []any{1.23}, "1.230"},
{"%#.4g", []any{0.123}, "0.1230"},
{"%#.4g", []any{1.2}, "1.200"},
{"%#.4g", []any{0.12}, "0.1200"},
{"%#.4g", []any{10.2}, "10.20"},
{"%#.4g", []any{0.0}, "0.000"},
{"%#.4g", []any{0.012}, "0.01200"},
{"%#.0f", []any{123.0}, "123."},
{"%#.0e", []any{123.0}, "1.e+02"},
{"%#.0x", []any{123.0}, "0x1.p+07"},
{"%#.0g", []any{123.0}, "1.e+02"},
{"%#.4f", []any{123.0}, "123.0000"},
{"%#.4e", []any{123.0}, "1.2300e+02"},
{"%#.4x", []any{123.0}, "0x1.ec00p+06"},
{"%#.4g", []any{123.0}, "123.0"},
{"%#.4g", []any{123000.0}, "1.230e+05"},
Even if minimalist, this library should follow the output of go/fmt
to be consistent with Go behaviors. Don't hesitate to follow / copy the fmt
library code in Go to match the behavior.
Also i got some weird behaviours from %010d
with -12345
-> 0000-12345
I will also check the gas issue. Since many projects import this package, we should take extra precautions regarding performance, but please do not compromise readability.
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
%i
doesn't seems to exist in go
, I don't think we should port it in gno
} | ||
stop := false | ||
for { | ||
x, ok := c2f[runes[*index]] |
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.
Hiding the reference inside a map complicates readability and performance. Why not use a direct switch case like this:
switch *index {
case '+'
case '-':
...
}
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.
because it's special characters, just after checking if it exists, i use the internal value of the flag to set it as the flag
x, ok := c2f[runes[*index]]
switch {
case ok:
p.meta.flags |= x // here
ptrForward(index, runes, 1)
so with map, i can check
- if it's a flag modifier character
- which flag do i need to trigger in the bit mask
maybe i can use a switch but idk if it will be better imo, what do you think ?
@paulogarithm did you have a chance to read guilhem's feedback? |
* E & e precision
* from: 6.41E+01 to: 6.410000E+01, this is go behavior
I'm so sorry i was putting all my efforts on Flamegraphs & EVM, I did not made any updates, but I'm back ! |
Co-authored-by: Guilhem Fanton <[email protected]> * the tests are from @gfanton recommandation
* 45+ test fails -> 39 test fails * todo: g flag, sign for scientific notations
ps: idk how to cherry pick so i copy pasted Co-authored-by: notJoon <[email protected]>
type flag uint8 | ||
|
||
const ( | ||
flagHashtag flag = 1 << iota |
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.
flagHashtag flag = 1 << iota | |
flagHash flag = 1 << iota |
@@ -32,13 +34,215 @@ func (b *buffer) writeRune(r rune) { | |||
*b = utf8.AppendRune(*b, r) | |||
} | |||
|
|||
// the different flags |
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.
// the different flags | |
// bitmap representing different flags |
lenH | ||
lenL | ||
lenLL | ||
lenZ |
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.
comments would be good, maybe even full names.
i := 0 | ||
for i < len(s) && unicode.IsDigit(rune(s[i])) { | ||
i++ | ||
} |
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.
i := 0 | |
for i < len(s) && unicode.IsDigit(rune(s[i])) { | |
i++ | |
} | |
var i int | |
for idx, c := range s { | |
if c < '0' || c > '9' { | |
i = idx | |
return | |
} | |
} |
Handles UTF-8 properly and more efficiently.
unicode.IsDigit
considers many more digits: https://www.fileformat.info/info/unicode/category/Nd/list.htm
return num, i, true | ||
} | ||
|
||
var flagMap = [256]flag{ |
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.
This would maybe be better adjacent to their types.
var lenMap = map[string]lengthModifier{ | ||
"ll": lenLL, | ||
"l": lenL, | ||
"h": lenH, | ||
"hh": lenHH, | ||
"z": lenZ, | ||
} |
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.
unused
case 'h', 'l', 'z': | ||
// Use lookahead to check for double-character length modifiers | ||
// like 'hh' (char) or 'll' (long long) without iterating through options | ||
if *index+1 < end { | ||
next := runes[*index+1] | ||
if c == 'h' && next == 'h' { | ||
p.meta.length = lenHH | ||
*index += 2 // Skip both characters | ||
continue | ||
} else if c == 'l' && next == 'l' { | ||
p.meta.length = lenLL | ||
*index += 2 // Skip both characters | ||
continue | ||
} | ||
} | ||
|
||
// Handle single-character length modifiers | ||
switch c { | ||
case 'h': | ||
p.meta.length = lenH | ||
case 'l': | ||
p.meta.length = lenL | ||
case 'z': | ||
p.meta.length = lenZ | ||
} | ||
*index++ |
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.
The length flags don't really seem to be doing anything now, but I don't think they should anywaY
Fixes
ufmt/printf
%.number
,%#
,%0
, and others