fmt: change evalutation of indexed arg to match docs

The old code put the index before the period in the precision;
it should be after so it's always before the star, as documented.
A little trickier to do in one pass but compensated for by more
tests and catching a couple of other error cases.

R=rsc
CC=golang-dev
https://golang.org/cl/9751044
This commit is contained in:
Rob Pike 2013-05-29 11:29:29 -04:00
parent ca986a2c81
commit d84132cce7
3 changed files with 58 additions and 30 deletions

View file

@ -131,7 +131,7 @@
For example,
fmt.Sprintf("%[2]d %[1]d\n", 11, 22)
will yield "22, 11", while
fmt.Sprintf("%[3]*[2].*[1]f", 12.0, 2, 6),
fmt.Sprintf("%[3]*.[2]*[1]f", 12.0, 2, 6),
equivalent to
fmt.Sprintf("%6.2f", 12.0),
will yield " 12.00". Because an explicit index affects subsequent verbs,
@ -155,8 +155,9 @@
Non-int for width or precision: %!(BADWIDTH) or %!(BADPREC)
Printf("%*s", 4.5, "hi"): %!(BADWIDTH)hi
Printf("%.*s", 4.5, "hi"): %!(BADPREC)hi
Invalid or out-of-range argument index: %!(BADARGNUM)
Printf("%*[2]d", 7): %d(BADARGNUM)
Invalid or invalid use of argument index: %!(BADINDEX)
Printf("%*[2]d", 7): %d(BADINDEX)
Printf("%.[2]d", 7): %d(BADINDEX)
All errors begin with the string "%!" followed sometimes
by a single character (the verb) and end with a parenthesized

View file

@ -550,18 +550,29 @@ var reorderTests = []struct {
{"%[2]d", SE{2, 1}, "1"},
{"%[2]d %[1]d", SE{1, 2}, "2 1"},
{"%[2]*[1]d", SE{2, 5}, " 2"},
{"%6.2f", SE{12.0}, " 12.00"},
{"%[3]*[2].*[1]f", SE{12.0, 2, 6}, " 12.00"},
{"%[1]*[2].*[3]f", SE{6, 2, 12.0}, " 12.00"},
{"%6.2f", SE{12.0}, " 12.00"}, // Explicit version of next line.
{"%[3]*.[2]*[1]f", SE{12.0, 2, 6}, " 12.00"},
{"%[1]*.[2]*[3]f", SE{6, 2, 12.0}, " 12.00"},
{"%10f", SE{12.0}, " 12.000000"},
{"%[1]*[3]f", SE{10, 99, 12.0}, " 12.000000"},
{"%.6f", SE{12.0}, "12.000000"}, // Explicit version of next line.
{"%.[1]*[3]f", SE{6, 99, 12.0}, "12.000000"},
{"%6.f", SE{12.0}, " 12"}, // // Explicit version of next line; empty precision means zero.
{"%[1]*.[3]f", SE{6, 3, 12.0}, " 12"},
// An actual use! Print the same arguments twice.
{"%d %d %d %#[1]o %#o %#o", SE{11, 12, 13}, "11 12 13 013 014 015"},
// Erroneous cases.
{"%[]d", SE{2, 1}, "%d(BADARGNUM)"},
{"%[-3]d", SE{2, 1}, "%d(BADARGNUM)"},
{"%[x]d", SE{2, 1}, "%d(BADARGNUM)"},
{"%[23]d", SE{2, 1}, "%d(BADARGNUM)"},
{"%[d", SE{2, 1}, "%d(BADINDEX)"},
{"%]d", SE{2, 1}, "%!](int=2)d%!(EXTRA int=1)"},
{"%[]d", SE{2, 1}, "%d(BADINDEX)"},
{"%[-3]d", SE{2, 1}, "%d(BADINDEX)"},
{"%[99]d", SE{2, 1}, "%d(BADINDEX)"},
{"%[3]", SE{2, 1}, "%!(NOVERB)"},
{"%[1].2d", SE{5, 6}, "%d(BADINDEX)"},
{"%[1]2d", SE{2, 1}, "%d(BADINDEX)"},
{"%3.[2]d", SE{7}, "%d(BADINDEX)"},
{"%.[2]d", SE{7}, "%d(BADINDEX)"},
{"%d %d %d %#[1]o %#o %#o %#o", SE{11, 12, 13}, "11 12 13 013 014 015 %o(MISSING)"},
}

View file

@ -22,7 +22,7 @@ var (
nilBytes = []byte("nil")
mapBytes = []byte("map[")
missingBytes = []byte("(MISSING)")
badArgNum = []byte("(BADARGNUM)")
badIndexBytes = []byte("(BADINDEX)")
panicBytes = []byte("(PANIC=")
extraBytes = []byte("%!(EXTRA ")
irparenBytes = []byte("i)")
@ -117,7 +117,7 @@ type pp struct {
value reflect.Value
// reordered records whether the format string used argument reordering.
reordered bool
// goodArgNum records whether the last reordering directive was valid.
// goodArgNum records whether all reordering directives were valid.
goodArgNum bool
runeBuf [utf8.UTFMax]byte
fmt fmt
@ -1021,11 +1021,11 @@ BigSwitch:
}
// intFromArg gets the argNumth element of a. On return, isInt reports whether the argument has type int.
func intFromArg(a []interface{}, end, i, argNum int) (num int, isInt bool, newi, newArgNum int) {
newi, newArgNum = end, argNum
if i < end && argNum < len(a) {
func intFromArg(a []interface{}, argNum int) (num int, isInt bool, newArgNum int) {
newArgNum = argNum
if argNum < len(a) {
num, isInt = a[argNum].(int)
newi, newArgNum = i+1, argNum+1
newArgNum = argNum + 1
}
return
}
@ -1053,24 +1053,25 @@ func parseArgNumber(format string) (index int, wid int, ok bool) {
// argNumber returns the next argument to evaluate, which is either the value of the passed-in
// argNum or the value of the bracketed integer that begins format[i:]. It also returns
// the new value of i, that is, the index of the next byte of the format to process.
func (p *pp) argNumber(argNum int, format string, i int, numArgs int) (newArgNum, newi int) {
p.goodArgNum = true
func (p *pp) argNumber(argNum int, format string, i int, numArgs int) (newArgNum, newi int, found bool) {
if len(format) <= i || format[i] != '[' {
return argNum, i
return argNum, i, false
}
p.reordered = true
index, wid, ok := parseArgNumber(format[i:])
if ok && 0 <= index && index < numArgs {
return index, i + wid
return index, i + wid, true
}
p.goodArgNum = false
return argNum, i + wid
return argNum, i + wid, true
}
func (p *pp) doPrintf(format string, a []interface{}) {
end := len(format)
argNum := 0 // we process one argument per non-trivial format
argNum := 0 // we process one argument per non-trivial format
afterIndex := false // previous item in format was an index like [3].
p.reordered = false
p.goodArgNum = true
for i := 0; i < end; {
lasti := i
for i < end && format[i] != '%' {
@ -1108,35 +1109,50 @@ func (p *pp) doPrintf(format string, a []interface{}) {
}
// Do we have an explicit argument index?
argNum, i = p.argNumber(argNum, format, i, len(a))
argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a))
// Do we have width?
if i < end && format[i] == '*' {
p.fmt.wid, p.fmt.widPresent, i, argNum = intFromArg(a, end, i, argNum)
i++
p.fmt.wid, p.fmt.widPresent, argNum = intFromArg(a, argNum)
if !p.fmt.widPresent {
p.buf.Write(badWidthBytes)
}
argNum, i = p.argNumber(argNum, format, i, len(a)) // We consumed []; another can follow here.
afterIndex = false
} else {
p.fmt.wid, p.fmt.widPresent, i = parsenum(format, i, end)
if afterIndex && p.fmt.widPresent { // "%[3]2d"
p.goodArgNum = false
}
}
// Do we have precision?
if i+1 < end && format[i] == '.' {
if format[i+1] == '*' {
p.fmt.prec, p.fmt.precPresent, i, argNum = intFromArg(a, end, i+1, argNum)
i++
if afterIndex { // "%[3].2d"
p.goodArgNum = false
}
argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a))
if format[i] == '*' {
i++
p.fmt.prec, p.fmt.precPresent, argNum = intFromArg(a, argNum)
if !p.fmt.precPresent {
p.buf.Write(badPrecBytes)
}
argNum, i = p.argNumber(argNum, format, i, len(a)) // We consumed []; another can follow here.
afterIndex = false
} else {
p.fmt.prec, p.fmt.precPresent, i = parsenum(format, i+1, end)
p.fmt.prec, p.fmt.precPresent, i = parsenum(format, i, end)
if !p.fmt.precPresent {
p.fmt.prec = 0
p.fmt.precPresent = true
}
}
}
if !afterIndex {
argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a))
}
if i >= end {
p.buf.Write(noVerbBytes)
continue
@ -1151,7 +1167,7 @@ func (p *pp) doPrintf(format string, a []interface{}) {
if !p.goodArgNum {
p.buf.WriteByte('%')
p.add(c)
p.buf.Write(badArgNum)
p.buf.Write(badIndexBytes)
continue
} else if argNum >= len(a) { // out of operands
p.buf.WriteByte('%')