Commit 7472ce0e authored by Rob Pike's avatar Rob Pike

fmt.Printf: introduce notation for random access to arguments.

This text is added to doc.go:

        Explicit argument indexes:

        In Printf, Sprintf, and Fprintf, the default behavior is for each
        formatting verb to format successive arguments passed in the call.
        However, the notation [n] immediately before the verb indicates that the
        nth one-indexed argument is to be formatted instead. The same notation
        before a '*' for a width or precision selects the argument index holding
        the value. After processing a bracketed expression [n], arguments n+1,
        n+2, etc. will be processed unless otherwise directed.

        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),
        equivalent to
                fmt.Sprintf("%6.2f", 12.0),
        will yield " 12.00". Because an explicit index affects subsequent verbs,
        this notation can be used to print the same values multiple times
        by resetting the index for the first argument to be repeated:
                fmt.Sprintf("%d %d %#[1]x %#x", 16, 17)
        will yield "16 17 0x10 0x11".

The notation chosen differs from that in C, but I believe it's easier to read
and to remember (we're indexing the arguments), and compatibility with
C's printf was never a strong goal anyway.

While we're here, change the word "field" to "arg" or "argument" in the
code; it was being misused and was confusing.

R=rsc, bradfitz, rogpeppe, minux.ma, peter.armitage
CC=golang-dev
https://golang.org/cl/9680043
parent ffe8a3c5
......@@ -8,4 +8,5 @@ Please keep the descriptions to a single line, starting with the
package or cmd/xxx directory name, and ending in a CL number.
Please keep the list sorted (as in sort.Strings of the lines).
fmt: indexed access to arguments in Printf etc. (CL 9680043).
io: Copy prioritizes WriterTo over ReaderFrom (CL 9462044).
......@@ -118,6 +118,28 @@
convert the value before recurring:
func (x X) String() string { return Sprintf("<%s>", string(x)) }
Explicit argument indexes:
In Printf, Sprintf, and Fprintf, the default behavior is for each
formatting verb to format successive arguments passed in the call.
However, the notation [n] immediately before the verb indicates that the
nth one-indexed argument is to be formatted instead. The same notation
before a '*' for a width or precision selects the argument index holding
the value. After processing a bracketed expression [n], arguments n+1,
n+2, etc. will be processed unless otherwise directed.
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),
equivalent to
fmt.Sprintf("%6.2f", 12.0),
will yield " 12.00". Because an explicit index affects subsequent verbs,
this notation can be used to print the same values multiple times
by resetting the index for the first argument to be repeated:
fmt.Sprintf("%d %d %#[1]x %#x", 16, 17)
will yield "16 17 0x10 0x11".
Format errors:
If an invalid argument is given for a verb, such as providing
......@@ -133,6 +155,8 @@
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)
All errors begin with the string "%!" followed sometimes
by a single character (the verb) and end with a parenthesized
......
......@@ -110,7 +110,7 @@ var bslice = barray[:]
var b byte
var fmttests = []struct {
var fmtTests = []struct {
fmt string
val interface{}
out string
......@@ -503,7 +503,7 @@ var fmttests = []struct {
}
func TestSprintf(t *testing.T) {
for _, tt := range fmttests {
for _, tt := range fmtTests {
s := Sprintf(tt.fmt, tt.val)
if i := strings.Index(tt.out, "PTR"); i >= 0 {
pattern := "PTR"
......@@ -539,6 +539,42 @@ func TestSprintf(t *testing.T) {
}
}
type SE []interface{} // slice of empty; notational compactness.
var reorderTests = []struct {
fmt string
val SE
out string
}{
{"%[1]d", SE{1}, "1"},
{"%[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"},
// 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)"},
{"%[3]", SE{2, 1}, "%!(NOVERB)"},
{"%d %d %d %#[1]o %#o %#o %#o", SE{11, 12, 13}, "11 12 13 013 014 015 %o(MISSING)"},
}
func TestReorder(t *testing.T) {
for _, tt := range reorderTests {
s := Sprintf(tt.fmt, tt.val...)
if s != tt.out {
t.Errorf("Sprintf(%q, %v) = <%s> want <%s>", tt.fmt, tt.val, s, tt.out)
} else {
}
}
}
func BenchmarkSprintfEmpty(b *testing.B) {
for i := 0; i < b.N; i++ {
Sprintf("")
......
This diff is collapsed.
......@@ -168,12 +168,12 @@ type ss struct {
// ssave holds the parts of ss that need to be
// saved and restored on recursive scans.
type ssave struct {
validSave bool // is or was a part of an actual ss.
nlIsEnd bool // whether newline terminates scan
nlIsSpace bool // whether newline counts as white space
fieldLimit int // max value of ss.count for this field; fieldLimit <= limit
limit int // max value of ss.count.
maxWid int // width of this field.
validSave bool // is or was a part of an actual ss.
nlIsEnd bool // whether newline terminates scan
nlIsSpace bool // whether newline counts as white space
argLimit int // max value of ss.count for this arg; argLimit <= limit
limit int // max value of ss.count.
maxWid int // width of this arg.
}
// The Read method is only in ScanState so that ScanState
......@@ -192,7 +192,7 @@ func (s *ss) ReadRune() (r rune, size int, err error) {
s.peekRune = -1
return
}
if s.atEOF || s.nlIsEnd && s.prevRune == '\n' || s.count >= s.fieldLimit {
if s.atEOF || s.nlIsEnd && s.prevRune == '\n' || s.count >= s.argLimit {
err = io.EOF
return
}
......@@ -389,7 +389,7 @@ func newScanState(r io.Reader, nlIsSpace, nlIsEnd bool) (s *ss, old ssave) {
s, ok := r.(*ss)
if ok {
old = s.ssave
s.limit = s.fieldLimit
s.limit = s.argLimit
s.nlIsEnd = nlIsEnd || s.nlIsEnd
s.nlIsSpace = nlIsSpace
return
......@@ -407,7 +407,7 @@ func newScanState(r io.Reader, nlIsSpace, nlIsEnd bool) (s *ss, old ssave) {
s.peekRune = -1
s.atEOF = false
s.limit = hugeWid
s.fieldLimit = hugeWid
s.argLimit = hugeWid
s.maxWid = hugeWid
s.validSave = true
s.count = 0
......@@ -477,8 +477,8 @@ func (s *ss) token(skipSpace bool, f func(rune) bool) []byte {
}
// typeError indicates that the type of the operand did not match the format
func (s *ss) typeError(field interface{}, expected string) {
s.errorString("expected field of type pointer to " + expected + "; found " + reflect.TypeOf(field).String())
func (s *ss) typeError(arg interface{}, expected string) {
s.errorString("expected argument of type pointer to " + expected + "; found " + reflect.TypeOf(arg).String())
}
var complexError = errors.New("syntax error scanning complex number")
......@@ -927,11 +927,11 @@ const floatVerbs = "beEfFgGv"
const hugeWid = 1 << 30
// scanOne scans a single value, deriving the scanner from the type of the argument.
func (s *ss) scanOne(verb rune, field interface{}) {
func (s *ss) scanOne(verb rune, arg interface{}) {
s.buf = s.buf[:0]
var err error
// If the parameter has its own Scan method, use that.
if v, ok := field.(Scanner); ok {
if v, ok := arg.(Scanner); ok {
err = v.Scan(s, verb)
if err != nil {
if err == io.EOF {
......@@ -942,7 +942,7 @@ func (s *ss) scanOne(verb rune, field interface{}) {
return
}
switch v := field.(type) {
switch v := arg.(type) {
case *bool:
*v = s.scanBool(verb)
case *complex64:
......@@ -1046,8 +1046,8 @@ func errorHandler(errp *error) {
// doScan does the real work for scanning without a format string.
func (s *ss) doScan(a []interface{}) (numProcessed int, err error) {
defer errorHandler(&err)
for _, field := range a {
s.scanOne('v', field)
for _, arg := range a {
s.scanOne('v', arg)
numProcessed++
}
// Check for newline if required.
......@@ -1144,9 +1144,9 @@ func (s *ss) doScanf(format string, a []interface{}) (numProcessed int, err erro
if !widPresent {
s.maxWid = hugeWid
}
s.fieldLimit = s.limit
if f := s.count + s.maxWid; f < s.fieldLimit {
s.fieldLimit = f
s.argLimit = s.limit
if f := s.count + s.maxWid; f < s.argLimit {
s.argLimit = f
}
c, w := utf8.DecodeRuneInString(format[i:])
......@@ -1156,11 +1156,11 @@ func (s *ss) doScanf(format string, a []interface{}) (numProcessed int, err erro
s.errorString("too few operands for format %" + format[i-w:])
break
}
field := a[numProcessed]
arg := a[numProcessed]
s.scanOne(c, field)
s.scanOne(c, arg)
numProcessed++
s.fieldLimit = s.limit
s.argLimit = s.limit
}
if numProcessed < len(a) {
s.errorString("too many operands")
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment