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 ...@@ -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. package or cmd/xxx directory name, and ending in a CL number.
Please keep the list sorted (as in sort.Strings of the lines). 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). io: Copy prioritizes WriterTo over ReaderFrom (CL 9462044).
...@@ -118,6 +118,28 @@ ...@@ -118,6 +118,28 @@
convert the value before recurring: convert the value before recurring:
func (x X) String() string { return Sprintf("<%s>", string(x)) } 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: Format errors:
If an invalid argument is given for a verb, such as providing If an invalid argument is given for a verb, such as providing
...@@ -133,6 +155,8 @@ ...@@ -133,6 +155,8 @@
Non-int for width or precision: %!(BADWIDTH) or %!(BADPREC) Non-int for width or precision: %!(BADWIDTH) or %!(BADPREC)
Printf("%*s", 4.5, "hi"): %!(BADWIDTH)hi Printf("%*s", 4.5, "hi"): %!(BADWIDTH)hi
Printf("%.*s", 4.5, "hi"): %!(BADPREC)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 All errors begin with the string "%!" followed sometimes
by a single character (the verb) and end with a parenthesized by a single character (the verb) and end with a parenthesized
......
...@@ -110,7 +110,7 @@ var bslice = barray[:] ...@@ -110,7 +110,7 @@ var bslice = barray[:]
var b byte var b byte
var fmttests = []struct { var fmtTests = []struct {
fmt string fmt string
val interface{} val interface{}
out string out string
...@@ -503,7 +503,7 @@ var fmttests = []struct { ...@@ -503,7 +503,7 @@ var fmttests = []struct {
} }
func TestSprintf(t *testing.T) { func TestSprintf(t *testing.T) {
for _, tt := range fmttests { for _, tt := range fmtTests {
s := Sprintf(tt.fmt, tt.val) s := Sprintf(tt.fmt, tt.val)
if i := strings.Index(tt.out, "PTR"); i >= 0 { if i := strings.Index(tt.out, "PTR"); i >= 0 {
pattern := "PTR" pattern := "PTR"
...@@ -539,6 +539,42 @@ func TestSprintf(t *testing.T) { ...@@ -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) { func BenchmarkSprintfEmpty(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
Sprintf("") Sprintf("")
......
This diff is collapsed.
...@@ -168,12 +168,12 @@ type ss struct { ...@@ -168,12 +168,12 @@ type ss struct {
// ssave holds the parts of ss that need to be // ssave holds the parts of ss that need to be
// saved and restored on recursive scans. // saved and restored on recursive scans.
type ssave struct { type ssave struct {
validSave bool // is or was a part of an actual ss. validSave bool // is or was a part of an actual ss.
nlIsEnd bool // whether newline terminates scan nlIsEnd bool // whether newline terminates scan
nlIsSpace bool // whether newline counts as white space nlIsSpace bool // whether newline counts as white space
fieldLimit int // max value of ss.count for this field; fieldLimit <= limit argLimit int // max value of ss.count for this arg; argLimit <= limit
limit int // max value of ss.count. limit int // max value of ss.count.
maxWid int // width of this field. maxWid int // width of this arg.
} }
// The Read method is only in ScanState so that ScanState // The Read method is only in ScanState so that ScanState
...@@ -192,7 +192,7 @@ func (s *ss) ReadRune() (r rune, size int, err error) { ...@@ -192,7 +192,7 @@ func (s *ss) ReadRune() (r rune, size int, err error) {
s.peekRune = -1 s.peekRune = -1
return 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 err = io.EOF
return return
} }
...@@ -389,7 +389,7 @@ func newScanState(r io.Reader, nlIsSpace, nlIsEnd bool) (s *ss, old ssave) { ...@@ -389,7 +389,7 @@ func newScanState(r io.Reader, nlIsSpace, nlIsEnd bool) (s *ss, old ssave) {
s, ok := r.(*ss) s, ok := r.(*ss)
if ok { if ok {
old = s.ssave old = s.ssave
s.limit = s.fieldLimit s.limit = s.argLimit
s.nlIsEnd = nlIsEnd || s.nlIsEnd s.nlIsEnd = nlIsEnd || s.nlIsEnd
s.nlIsSpace = nlIsSpace s.nlIsSpace = nlIsSpace
return return
...@@ -407,7 +407,7 @@ func newScanState(r io.Reader, nlIsSpace, nlIsEnd bool) (s *ss, old ssave) { ...@@ -407,7 +407,7 @@ func newScanState(r io.Reader, nlIsSpace, nlIsEnd bool) (s *ss, old ssave) {
s.peekRune = -1 s.peekRune = -1
s.atEOF = false s.atEOF = false
s.limit = hugeWid s.limit = hugeWid
s.fieldLimit = hugeWid s.argLimit = hugeWid
s.maxWid = hugeWid s.maxWid = hugeWid
s.validSave = true s.validSave = true
s.count = 0 s.count = 0
...@@ -477,8 +477,8 @@ func (s *ss) token(skipSpace bool, f func(rune) bool) []byte { ...@@ -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 // typeError indicates that the type of the operand did not match the format
func (s *ss) typeError(field interface{}, expected string) { func (s *ss) typeError(arg interface{}, expected string) {
s.errorString("expected field of type pointer to " + expected + "; found " + reflect.TypeOf(field).String()) s.errorString("expected argument of type pointer to " + expected + "; found " + reflect.TypeOf(arg).String())
} }
var complexError = errors.New("syntax error scanning complex number") var complexError = errors.New("syntax error scanning complex number")
...@@ -927,11 +927,11 @@ const floatVerbs = "beEfFgGv" ...@@ -927,11 +927,11 @@ const floatVerbs = "beEfFgGv"
const hugeWid = 1 << 30 const hugeWid = 1 << 30
// scanOne scans a single value, deriving the scanner from the type of the argument. // 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] s.buf = s.buf[:0]
var err error var err error
// If the parameter has its own Scan method, use that. // 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) err = v.Scan(s, verb)
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
...@@ -942,7 +942,7 @@ func (s *ss) scanOne(verb rune, field interface{}) { ...@@ -942,7 +942,7 @@ func (s *ss) scanOne(verb rune, field interface{}) {
return return
} }
switch v := field.(type) { switch v := arg.(type) {
case *bool: case *bool:
*v = s.scanBool(verb) *v = s.scanBool(verb)
case *complex64: case *complex64:
...@@ -1046,8 +1046,8 @@ func errorHandler(errp *error) { ...@@ -1046,8 +1046,8 @@ func errorHandler(errp *error) {
// doScan does the real work for scanning without a format string. // doScan does the real work for scanning without a format string.
func (s *ss) doScan(a []interface{}) (numProcessed int, err error) { func (s *ss) doScan(a []interface{}) (numProcessed int, err error) {
defer errorHandler(&err) defer errorHandler(&err)
for _, field := range a { for _, arg := range a {
s.scanOne('v', field) s.scanOne('v', arg)
numProcessed++ numProcessed++
} }
// Check for newline if required. // Check for newline if required.
...@@ -1144,9 +1144,9 @@ func (s *ss) doScanf(format string, a []interface{}) (numProcessed int, err erro ...@@ -1144,9 +1144,9 @@ func (s *ss) doScanf(format string, a []interface{}) (numProcessed int, err erro
if !widPresent { if !widPresent {
s.maxWid = hugeWid s.maxWid = hugeWid
} }
s.fieldLimit = s.limit s.argLimit = s.limit
if f := s.count + s.maxWid; f < s.fieldLimit { if f := s.count + s.maxWid; f < s.argLimit {
s.fieldLimit = f s.argLimit = f
} }
c, w := utf8.DecodeRuneInString(format[i:]) c, w := utf8.DecodeRuneInString(format[i:])
...@@ -1156,11 +1156,11 @@ func (s *ss) doScanf(format string, a []interface{}) (numProcessed int, err erro ...@@ -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:]) s.errorString("too few operands for format %" + format[i-w:])
break break
} }
field := a[numProcessed] arg := a[numProcessed]
s.scanOne(c, field) s.scanOne(c, arg)
numProcessed++ numProcessed++
s.fieldLimit = s.limit s.argLimit = s.limit
} }
if numProcessed < len(a) { if numProcessed < len(a) {
s.errorString("too many operands") 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