Commit 4fc97c47 authored by Rob Pike's avatar Rob Pike

fmt.Scanf: implement formats, provide Sscanf (strings)

- provide convenience functions for scanning strings
- enable Scanf etc.
- update doc comments

R=rsc
CC=golang-dev
https://golang.org/cl/1451044
parent 8ae29642
...@@ -81,18 +81,45 @@ ...@@ -81,18 +81,45 @@
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 Fscanln
read from a specified os.Reader. By default, tokens are read from a specified os.Reader; Sscan and Sscanln read from
separated by spaces. Fscanln and Scanln stop scanning at a an argument string. By default, tokens are separated by
spaces. Sscanln, Fscanln and Sscanln stop scanning at a
newline and require that the items be followed by one; the newline and require that the items be followed by one; the
other routines treat newlines as spaces. other routines treat newlines as spaces.
If an operand implements method Scan() (that is, it implements Scanf, Fscanf, and Sscanf parse the arguments according to a
the Scanner interface) that method will be used to scan the format string, analogous to that of Printf. For example, "%x"
text for that operand. will scan an integer as a hexadecimal number, and %v will scan
the default representation format for the value.
The formats behave analogously to those of Printf with the
following exceptions:
%p is not implemented
%T is not implemented
%e %E %f %F %g %g are all equivalent and scan any floating
point or complex value
When scanning with a format, all non-empty runs of space
characters (including newline) are equivalent to a single
space in both the format and the input. With that proviso,
text in the format string must match the input text; scanning
stops if it does not, with the return value of the function
indicating the number of arguments scanned.
In all the scanning functions, if an operand implements method
Scan (that is, it implements the Scanner interface) that
method will be used to scan the text for that operand. Also,
if the number of arguments scanned is less than the number of
arguments provided, an error is returned.
All arguments to be scanned must be either pointers to basic
types or implementations of the Scanner interface.
*/ */
package fmt package fmt
// BUG(r): There is no format-driven scanning yet. // BUG: format precision and flags are not yet implemented for scanning.
// BUG: %sqx are not yet implemented for scanning byte slices.
import ( import (
"bytes" "bytes"
......
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"os" "os"
"reflect" "reflect"
"strconv" "strconv"
"strings"
"unicode" "unicode"
"utf8" "utf8"
) )
...@@ -41,31 +42,52 @@ type Scanner interface { ...@@ -41,31 +42,52 @@ type Scanner interface {
Scan(ScanState) os.Error Scan(ScanState) os.Error
} }
// Scan parses text read from standard input, storing successive // Scan scans text read from standard input, storing successive
// space-separated values into successive arguments. Newlines count as // space-separated values into successive arguments. Newlines count
// space. Each argument must be a pointer to a basic type or an // as space. It returns the number of items successfully scanned.
// implementation of the Scanner interface. It returns the number of items // If that is less than the number of arguments, err will report why.
// successfully parsed. If that is less than the number of arguments, err
// will report why.
func Scan(a ...interface{}) (n int, err os.Error) { func Scan(a ...interface{}) (n int, err os.Error) {
return Fscan(os.Stdin, a) return Fscan(os.Stdin, a)
} }
// Fscanln parses text read from standard input, storing successive // Scanln is similar to Scan, but stops scanning at a newline and
// space-separated values into successive arguments. Scanning stops at a // after the final item there must be a newline or EOF.
// newline and after the final item there must be a newline or EOF. Each
// argument must be a pointer to a basic type or an implementation of the
// Scanner interface. It returns the number of items successfully parsed.
// If that is less than the number of arguments, err will report why.
func Scanln(a ...interface{}) (n int, err os.Error) { func Scanln(a ...interface{}) (n int, err os.Error) {
return Fscanln(os.Stdin, a) return Fscanln(os.Stdin, a)
} }
// Fscan parses text read from r, storing successive space-separated values // Scanf scans text read from standard input, storing successive
// into successive arguments. Newlines count as space. Each argument must // space-separated values into successive arguments as determined by
// be a pointer to a basic type or an implementation of the Scanner // the format. It returns the number of items successfully scanned.
// interface. It returns the number of items successfully parsed. If that func Scanf(format string, a ...interface{}) (n int, err os.Error) {
// is less than the number of arguments, err will report why. return Fscanf(os.Stdin, format, a)
}
// Sscan scans the argument string, storing successive space-separated
// values into successive arguments. Newlines count as space. It
// returns the number of items successfully scanned. If that is less
// than the number of arguments, err will report why.
func Sscan(str string, a ...interface{}) (n int, err os.Error) {
return Fscan(strings.NewReader(str), a)
}
// Sscanln is similar to Sscan, but stops scanning at a newline and
// after the final item there must be a newline or EOF.
func Sscanln(str string, a ...interface{}) (n int, err os.Error) {
return Fscanln(strings.NewReader(str), a)
}
// Sscanf scans the argument string, storing successive space-separated
// values into successive arguments as determined by the format. It
// returns the number of items successfully parsed.
func Sscanf(str string, format string, a ...interface{}) (n int, err os.Error) {
return Fscanf(strings.NewReader(str), format, a)
}
// Fscan scans text read from r, storing successive space-separated
// values into successive arguments. Newlines count as space. It
// returns the number of items successfully scanned. If that is less
// than the number of arguments, err will report why.
func Fscan(r io.Reader, a ...interface{}) (n int, err os.Error) { func Fscan(r io.Reader, a ...interface{}) (n int, err os.Error) {
s := newScanState(r, true) s := newScanState(r, true)
n, err = s.doScan(a) n, err = s.doScan(a)
...@@ -73,12 +95,8 @@ func Fscan(r io.Reader, a ...interface{}) (n int, err os.Error) { ...@@ -73,12 +95,8 @@ func Fscan(r io.Reader, a ...interface{}) (n int, err os.Error) {
return return
} }
// Fscanln parses text read from r, storing successive space-separated values // Fscanln is similar to Fscan, but stops scanning at a newline and
// into successive arguments. Scanning stops at a newline and after the // after the final item there must be a newline or EOF.
// final item there must be a newline or EOF. Each argument must be a
// pointer to a basic type or an implementation of the Scanner interface. It
// returns the number of items successfully parsed. If that is less than the
// number of arguments, err will report why.
func Fscanln(r io.Reader, a ...interface{}) (n int, err os.Error) { func Fscanln(r io.Reader, a ...interface{}) (n int, err os.Error) {
s := newScanState(r, false) s := newScanState(r, false)
n, err = s.doScan(a) n, err = s.doScan(a)
...@@ -86,13 +104,10 @@ func Fscanln(r io.Reader, a ...interface{}) (n int, err os.Error) { ...@@ -86,13 +104,10 @@ func Fscanln(r io.Reader, a ...interface{}) (n int, err os.Error) {
return return
} }
// XXXScanf is incomplete, do not use. // Fscanf scans text read from r, storing successive space-separated
func XXXScanf(format string, a ...interface{}) (n int, err os.Error) { // values into successive arguments as determined by the format. It
return XXXFscanf(os.Stdin, format, a) // returns the number of items successfully parsed.
} func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err os.Error) {
// XXXFscanf is incomplete, do not use.
func XXXFscanf(r io.Reader, format string, a ...interface{}) (n int, err os.Error) {
s := newScanState(r, false) s := newScanState(r, false)
n, err = s.doScanf(format, a) n, err = s.doScanf(format, a)
s.free() s.free()
...@@ -723,6 +738,53 @@ func (s *ss) doScan(a []interface{}) (numProcessed int, err os.Error) { ...@@ -723,6 +738,53 @@ func (s *ss) doScan(a []interface{}) (numProcessed int, err os.Error) {
return return
} }
// advance determines whether the next characters in the input matches
// those of the format. It returns the number of bytes (sic) consumed
// in the format. Newlines included, all runs of space characters in
// either input or format behave as a single space. This routines also
// handles the %% case. If the return value is zero, either the format
// is sitting on a % or the input is empty.
func (s *ss) advance(format string) (i int) {
for i < len(format) {
fmtc, w := utf8.DecodeRuneInString(format[i:])
if fmtc == '%' {
// %% acts like a real percent
nextc, _ := utf8.DecodeRuneInString(format[i+w:]) // will not match % if string is empty
if nextc != '%' {
return
}
i += w // skip the first %
}
sawSpace := false
for unicode.IsSpace(fmtc) && i < len(format) {
sawSpace = true
i += w
fmtc, w = utf8.DecodeRuneInString(format[i:])
}
if sawSpace {
// There was space in the format, so there should be space (EOF)
// in the input.
inputc := s.getRune()
if inputc == EOF {
return
}
if !unicode.IsSpace(inputc) {
// Space in format but not in input: error
s.errorString("expected space in input to match format")
}
s.skipSpace()
continue
}
inputc := s.mustGetRune()
if fmtc != inputc {
s.UngetRune(inputc)
return
}
i += w
}
return
}
// doScanf does the real work when scanning with a format string. // doScanf does the real work when scanning with a format string.
// At the moment, it handles only pointers to basic types. // At the moment, it handles only pointers to basic types.
func (s *ss) doScanf(format string, a []interface{}) (numProcessed int, err os.Error) { func (s *ss) doScanf(format string, a []interface{}) (numProcessed int, err os.Error) {
...@@ -730,21 +792,24 @@ func (s *ss) doScanf(format string, a []interface{}) (numProcessed int, err os.E ...@@ -730,21 +792,24 @@ func (s *ss) doScanf(format string, a []interface{}) (numProcessed int, err os.E
end := len(format) - 1 end := len(format) - 1
// We process one item per non-trivial format // We process one item per non-trivial format
for i := 0; i <= end; { for i := 0; i <= end; {
c, w := utf8.DecodeRuneInString(format[i:]) w := s.advance(format[i:])
if c != '%' || i == end { if w > 0 {
// TODO: WHAT NOW?
i += w i += w
continue continue
} }
i++ // Either we have a percent character or we ran out of input.
if format[i] != '%' {
// Out of format. Have we run out of input?
if i < len(a) {
s.errorString("too many arguments for format")
}
break
}
i++ // % is one byte
// TODO: FLAGS // TODO: FLAGS
c, w = utf8.DecodeRuneInString(format[i:]) c, w := utf8.DecodeRuneInString(format[i:])
i += w i += w
// percent is special - absorbs no operand
if c == '%' {
// TODO: WHAT NOW?
continue
}
if numProcessed >= len(a) { // out of operands if numProcessed >= len(a) { // out of operands
s.errorString("too few operands for format %" + format[i-w:]) s.errorString("too few operands for format %" + format[i-w:])
......
...@@ -220,6 +220,10 @@ var scanfTests = []ScanfTest{ ...@@ -220,6 +220,10 @@ var scanfTests = []ScanfTest{
ScanfTest{"%g", "11+6e1i\n", &renamedComplex64Val, renamedComplex64(11 + 6e1i)}, ScanfTest{"%g", "11+6e1i\n", &renamedComplex64Val, renamedComplex64(11 + 6e1i)},
ScanfTest{"%g", "-11.+7e+1i", &renamedComplex128Val, renamedComplex128(-11. + 7e+1i)}, ScanfTest{"%g", "-11.+7e+1i", &renamedComplex128Val, renamedComplex128(-11. + 7e+1i)},
// Interesting formats
ScanfTest{"here is\tthe value:%d", "here is the\tvalue:118\n", &intVal, 118},
ScanfTest{"%% %%:%d", "% %:119\n", &intVal, 119},
ScanfTest{"%x", "FFFFFFFF\n", &uint32Val, uint32(0xFFFFFFFF)}, ScanfTest{"%x", "FFFFFFFF\n", &uint32Val, uint32(0xFFFFFFFF)},
} }
...@@ -271,8 +275,7 @@ func TestScanln(t *testing.T) { ...@@ -271,8 +275,7 @@ func TestScanln(t *testing.T) {
func TestScanf(t *testing.T) { func TestScanf(t *testing.T) {
for _, test := range scanfTests { for _, test := range scanfTests {
r := strings.NewReader(test.text) n, err := Sscanf(test.text, test.format, test.in)
n, err := XXXFscanf(r, test.format, test.in)
if err != nil { if err != nil {
t.Errorf("got error scanning (%q, %q): %s", test.format, test.text, err) t.Errorf("got error scanning (%q, %q): %s", test.format, test.text, err)
continue continue
...@@ -297,8 +300,7 @@ func TestScanOverflow(t *testing.T) { ...@@ -297,8 +300,7 @@ func TestScanOverflow(t *testing.T) {
// different machines and different types report errors with different strings. // different machines and different types report errors with different strings.
re := testing.MustCompile("overflow|too large|out of range|not representable") re := testing.MustCompile("overflow|too large|out of range|not representable")
for _, test := range overflowTests { for _, test := range overflowTests {
r := strings.NewReader(test.text) _, err := Sscan(test.text, test.in)
_, err := Fscan(r, test.in)
if err == nil { if err == nil {
t.Errorf("expected overflow scanning %q", test.text) t.Errorf("expected overflow scanning %q", test.text)
continue continue
...@@ -310,15 +312,39 @@ func TestScanOverflow(t *testing.T) { ...@@ -310,15 +312,39 @@ func TestScanOverflow(t *testing.T) {
} }
func TestScanMultiple(t *testing.T) { func TestScanMultiple(t *testing.T) {
text := "1 2 3 x" text := "1 2 3"
r := strings.NewReader(text) r := strings.NewReader(text)
var a, b, c, d int var a, b, c, d int
n, err := Fscan(r, &a, &b, &c, &d) n, err := Fscan(r, &a, &b, &c)
if n != 3 {
t.Errorf("Fscan count error: expected 3: got %d", n)
}
if err != nil {
t.Errorf("Fscan expected no error scanning %q; got %s", text, 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"
r = strings.NewReader(text)
n, err = Fscanf(r, "%d %d %d\n", &a, &b, &c, &d)
if n != 3 { if n != 3 {
t.Errorf("count error: expected 3: got %d", n) 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 { if err == nil {
t.Errorf("expected error scanning ", text) t.Errorf("Fscanf expected error scanning %q", text)
} }
} }
...@@ -334,9 +360,8 @@ func TestScanNotPointer(t *testing.T) { ...@@ -334,9 +360,8 @@ func TestScanNotPointer(t *testing.T) {
} }
func TestScanlnNoNewline(t *testing.T) { func TestScanlnNoNewline(t *testing.T) {
r := strings.NewReader("1 x\n")
var a int var a int
_, err := Fscanln(r, &a) _, err := Sscanln("1 x\n", &a)
if err == nil { if err == nil {
t.Error("expected error scanning string missing newline") t.Error("expected error scanning string missing newline")
} else if strings.Index(err.String(), "newline") < 0 { } else if strings.Index(err.String(), "newline") < 0 {
......
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