Commit 962e8b87 authored by Rob Pike's avatar Rob Pike

fmt.Scan: field widths

Also fix an interface bug: white-space-delimited doesn't work well for cases like "%d, %d" on "23, 23")

R=rsc
CC=golang-dev
https://golang.org/cl/1502041
parent 3fb8d2ad
...@@ -80,12 +80,11 @@ ...@@ -80,12 +80,11 @@
Scanning: Scanning:
An analogous set of functions scans formatted text to yield An analogous set of functions scans formatted text to yield
values. Scan and Scanln read from os.Stdin; Fscan and Fscanln values. Scan and Scanln read from os.Stdin; Fscan and
read from a specified os.Reader; Sscan and Sscanln read from Fscanln read from a specified os.Reader; Sscan and Sscanln
an argument string. By default, tokens are separated by read from an argument string. Sscanln, Fscanln and Sscanln
spaces. Sscanln, Fscanln and Sscanln stop scanning at a stop scanning at a newline and require that the items be
newline and require that the items be followed by one; the followed by one; the other routines treat newlines as spaces.
other routines treat newlines as spaces.
Scanf, Fscanf, and Sscanf parse the arguments according to a Scanf, Fscanf, and Sscanf parse the arguments according to a
format string, analogous to that of Printf. For example, "%x" format string, analogous to that of Printf. For example, "%x"
...@@ -99,6 +98,12 @@ ...@@ -99,6 +98,12 @@
%T is not implemented %T is not implemented
%e %E %f %F %g %g are all equivalent and scan any floating %e %E %f %F %g %g are all equivalent and scan any floating
point or complex value point or complex value
%s and %v on strings scan a space-delimited token
Width is interpreted in the input text (%5s means at most
five runes of input will be read to scan a string) but there
is no syntax for scanning with a precision (no %5.2f, just
%5f).
When scanning with a format, all non-empty runs of space When scanning with a format, all non-empty runs of space
characters (including newline) are equivalent to a single characters (including newline) are equivalent to a single
...@@ -118,8 +123,6 @@ ...@@ -118,8 +123,6 @@
*/ */
package fmt package fmt
// BUG: format precision and flags are not yet implemented for scanning.
import ( import (
"bytes" "bytes"
"io" "io"
......
This diff is collapsed.
...@@ -26,6 +26,14 @@ type ScanfTest struct { ...@@ -26,6 +26,14 @@ type ScanfTest struct {
out interface{} out interface{}
} }
type ScanfMultiTest struct {
format string
text string
in []interface{}
out []interface{}
err string
}
type ( type (
renamedBool bool renamedBool bool
renamedInt int renamedInt int
...@@ -65,6 +73,7 @@ var ( ...@@ -65,6 +73,7 @@ var (
float32Val float32 float32Val float32
float64Val float64 float64Val float64
stringVal string stringVal string
stringVal1 string
bytesVal []byte bytesVal []byte
complexVal complex complexVal complex
complex64Val complex64 complex64Val complex64
...@@ -91,17 +100,29 @@ var ( ...@@ -91,17 +100,29 @@ var (
renamedComplex128Val renamedComplex128 renamedComplex128Val renamedComplex128
) )
// Xs accepts any non-empty run of x's. // Xs accepts any non-empty run of the verb character
var xPat = testing.MustCompile("x+")
type Xs string type Xs string
func (x *Xs) Scan(state ScanState) os.Error { func (x *Xs) Scan(state ScanState, verb int) os.Error {
tok, err := state.Token() var tok string
var c int
var err os.Error
wid, present := state.Width()
if !present {
tok, err = state.Token()
} else {
for i := 0; i < wid; i++ {
c, err = state.GetRune()
if err != nil {
break
}
tok += string(c)
}
}
if err != nil { if err != nil {
return err return err
} }
if !xPat.MatchString(tok) { if !testing.MustCompile(string(verb) + "+").MatchString(tok) {
return os.ErrorString("syntax error for xs") return os.ErrorString("syntax error for xs")
} }
*x = Xs(tok) *x = Xs(tok)
...@@ -169,7 +190,7 @@ var scanTests = []ScanTest{ ...@@ -169,7 +190,7 @@ var scanTests = []ScanTest{
ScanTest{"115\n", &renamedBytesVal, renamedBytes([]byte("115"))}, ScanTest{"115\n", &renamedBytesVal, renamedBytes([]byte("115"))},
// Custom scanner. // Custom scanner.
ScanTest{" xxx ", &xVal, Xs("xxx")}, ScanTest{" vvv ", &xVal, Xs("vvv")},
} }
var scanfTests = []ScanfTest{ var scanfTests = []ScanfTest{
...@@ -178,7 +199,7 @@ var scanfTests = []ScanfTest{ ...@@ -178,7 +199,7 @@ var scanfTests = []ScanfTest{
ScanfTest{"%v", "-71\n", &intVal, -71}, ScanfTest{"%v", "-71\n", &intVal, -71},
ScanfTest{"%d", "72\n", &intVal, 72}, ScanfTest{"%d", "72\n", &intVal, 72},
ScanfTest{"%d", "73\n", &int8Val, int8(73)}, ScanfTest{"%d", "73\n", &int8Val, int8(73)},
ScanfTest{"%d", "-74\n", &int16Val, int16(-74)}, ScanfTest{"%d", "+74\n", &int16Val, int16(74)},
ScanfTest{"%d", "75\n", &int32Val, int32(75)}, ScanfTest{"%d", "75\n", &int32Val, int32(75)},
ScanfTest{"%d", "76\n", &int64Val, int64(76)}, ScanfTest{"%d", "76\n", &int64Val, int64(76)},
ScanfTest{"%b", "1001001\n", &intVal, 73}, ScanfTest{"%b", "1001001\n", &intVal, 73},
...@@ -236,7 +257,12 @@ var scanfTests = []ScanfTest{ ...@@ -236,7 +257,12 @@ var scanfTests = []ScanfTest{
ScanfTest{"here is\tthe value:%d", "here is the\tvalue:118\n", &intVal, 118}, ScanfTest{"here is\tthe value:%d", "here is the\tvalue:118\n", &intVal, 118},
ScanfTest{"%% %%:%d", "% %:119\n", &intVal, 119}, ScanfTest{"%% %%:%d", "% %:119\n", &intVal, 119},
// Corner cases
ScanfTest{"%x", "FFFFFFFF\n", &uint32Val, uint32(0xFFFFFFFF)}, ScanfTest{"%x", "FFFFFFFF\n", &uint32Val, uint32(0xFFFFFFFF)},
// Custom scanner.
ScanfTest{"%s", " sss ", &xVal, Xs("sss")},
ScanfTest{"%2s", "sssss", &xVal, Xs("ss")},
} }
var overflowTests = []ScanTest{ var overflowTests = []ScanTest{
...@@ -253,6 +279,34 @@ var overflowTests = []ScanTest{ ...@@ -253,6 +279,34 @@ var overflowTests = []ScanTest{
ScanTest{"(1-1e500i)", &complex128Val, 0}, ScanTest{"(1-1e500i)", &complex128Val, 0},
} }
var i, j, k int
var f float
var s, t string
var c complex
var x, y Xs
func args(a ...interface{}) []interface{} { return a }
var multiTests = []ScanfMultiTest{
ScanfMultiTest{"", "", nil, nil, ""},
ScanfMultiTest{"%d", "23", args(&i), args(23), ""},
ScanfMultiTest{"%2s%3s", "22333", args(&s, &t), args("22", "333"), ""},
ScanfMultiTest{"%2d%3d", "44555", args(&i, &j), args(44, 555), ""},
ScanfMultiTest{"%2d.%3d", "66.777", args(&i, &j), args(66, 777), ""},
ScanfMultiTest{"%d, %d", "23, 18", args(&i, &j), args(23, 18), ""},
ScanfMultiTest{"%3d22%3d", "33322333", args(&i, &j), args(333, 333), ""},
ScanfMultiTest{"%6vX=%3fY", "3+2iX=2.5Y", args(&c, &f), args((3 + 2i), float(2.5)), ""},
ScanfMultiTest{"%d%s", "123abc", args(&i, &s), args(123, "abc"), ""},
// Custom scanner.
ScanfMultiTest{"%2e%f", "eefffff", []interface{}{&x, &y}, []interface{}{Xs("ee"), Xs("fffff")}, ""},
// Errors
ScanfMultiTest{"%t", "23 18", []interface{}{&i}, nil, "bad verb"},
ScanfMultiTest{"%d %d %d", "23 18", []interface{}{&i, &j}, []interface{}{23, 18}, "too few operands"},
ScanfMultiTest{"%d %d", "23 18 27", []interface{}{&i, &j, &k}, []interface{}{23, 18}, "too many operands"},
}
func testScan(t *testing.T, scan func(r io.Reader, a ...interface{}) (int, os.Error)) { func testScan(t *testing.T, scan func(r io.Reader, a ...interface{}) (int, os.Error)) {
for _, test := range scanTests { for _, test := range scanTests {
r := strings.NewReader(test.text) r := strings.NewReader(test.text)
...@@ -323,40 +377,59 @@ func TestScanOverflow(t *testing.T) { ...@@ -323,40 +377,59 @@ func TestScanOverflow(t *testing.T) {
} }
} }
// TODO: there's no conversion from []T to ...T, but we can fake it. These
// functions do the faking. We index the table by the length of the param list.
var scanf = []func(string, string, []interface{}) (int, os.Error){
0: func(s, f string, i []interface{}) (int, os.Error) { return Sscanf(s, f) },
1: func(s, f string, i []interface{}) (int, os.Error) { return Sscanf(s, f, i[0]) },
2: func(s, f string, i []interface{}) (int, os.Error) { return Sscanf(s, f, i[0], i[1]) },
3: func(s, f string, i []interface{}) (int, os.Error) { return Sscanf(s, f, i[0], i[1], i[2]) },
}
func TestScanfMulti(t *testing.T) {
sliceType := reflect.Typeof(make([]interface{}, 1)).(*reflect.SliceType)
for _, test := range multiTests {
n, err := scanf[len(test.in)](test.text, test.format, test.in)
if err != nil {
if test.err == "" {
t.Errorf("got error scanning (%q, %q): %q", test.format, test.text, err)
} else if strings.Index(err.String(), test.err) < 0 {
t.Errorf("got wrong error scanning (%q, %q): %q; expected %q", test.format, test.text, err, test.err)
}
continue
}
if test.err != "" {
t.Errorf("expected error %q error scanning (%q, %q)", test.err, test.format, test.text)
}
if n != len(test.out) {
t.Errorf("count error on entry (%q, %q): expected %d got %d", test.format, test.text, len(test.out), n)
continue
}
// Convert the slice of pointers into a slice of values
resultVal := reflect.MakeSlice(sliceType, n, n)
for i := 0; i < n; i++ {
v := reflect.NewValue(test.in[i]).(*reflect.PtrValue).Elem()
resultVal.Elem(i).(*reflect.InterfaceValue).Set(v)
}
result := resultVal.Interface()
if !reflect.DeepEqual(result, test.out) {
t.Errorf("scanning (%q, %q): expected %v got %v", test.format, test.text, test.out, result)
}
}
}
func TestScanMultiple(t *testing.T) { func TestScanMultiple(t *testing.T) {
text := "1 2 3" var a int
r := strings.NewReader(text) var s string
var a, b, c, d int n, err := Sscan("123abc", &a, &s)
n, err := Fscan(r, &a, &b, &c) if n != 2 {
if n != 3 { t.Errorf("Sscan count error: expected 2: got %d", n)
t.Errorf("Fscan count error: expected 3: got %d", n)
} }
if err != nil { if err != nil {
t.Errorf("Fscan expected no error scanning %q; got %s", text, err) t.Errorf("Sscan expected no error; got %s", err)
}
text = "1 2 3 x"
r = strings.NewReader(text)
n, err = Fscan(r, &a, &b, &c, &d)
if n != 3 {
t.Errorf("Fscan count error: expected 3: got %d", n)
}
if err == nil {
t.Errorf("Fscan expected error scanning %q", text)
} }
text = "1 2 3 x" if a != 123 || s != "abc" {
r = strings.NewReader(text) t.Errorf("Sscan wrong values: got (%d %q) expected (123 \"abc\")", a, s)
n, err = Fscanf(r, "%d %d %d\n", &a, &b, &c, &d)
if n != 3 {
t.Errorf("Fscanf count error: expected 3: got %d", n)
}
text = "1 2"
r = strings.NewReader(text)
n, err = Fscanf(r, "%d %d %d\n", &a, &b, &c, &d)
if n != 2 {
t.Errorf("Fscanf count error: expected 2: got %d", n)
}
if err == nil {
t.Errorf("Fscanf expected error scanning %q", text)
} }
} }
......
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