]> Cypherpunks repositories - gostls13.git/commitdiff
fmt: stop giving characters to the Scan method of Scanner
authorRob Pike <r@golang.org>
Thu, 24 Feb 2011 18:14:19 +0000 (10:14 -0800)
committerRob Pike <r@golang.org>
Thu, 24 Feb 2011 18:14:19 +0000 (10:14 -0800)
when we hit a newline during *scanln routines.

Fixes #1490.

R=rsc
CC=golang-dev
https://golang.org/cl/4209042

src/pkg/fmt/doc.go
src/pkg/fmt/scan.go
src/pkg/fmt/scan_test.go

index b40e265ae92766155e884998ce09a5cf5975e5e2..66947b77ceeef0fe23af34756b1070d952ac8f1f 100644 (file)
        An analogous set of functions scans formatted text to yield
        values.  Scan, Scanf and Scanln read from os.Stdin; Fscan,
        Fscanf and Fscanln read from a specified os.Reader; Sscan,
-       Sscanf and Sscanln read from an argument string.  Sscanln,
+       Sscanf and Sscanln read from an argument string.  Scanln,
        Fscanln and Sscanln stop scanning at a newline and require that
        the items be followed by one; Sscanf, Fscanf and Sscanf require
        newlines in the input to match newlines in the format; the other
index 53d88d574d7662e3b581ac76cc5fdd18d236fc25..ed539a26f729fdb4be489f77c3aad0ff4115e4ab 100644 (file)
@@ -29,6 +29,8 @@ type runeUnreader interface {
 // to discover the next space-delimited token.
 type ScanState interface {
        // GetRune reads the next rune (Unicode code point) from the input.
+       // If invoked during Scanln, Fscanln, or Sscanln, GetRune() will
+       // return EOF after returning the first '\n'.
        GetRune() (rune int, err os.Error)
        // UngetRune causes the next call to GetRune to return the same rune.
        UngetRune()
@@ -44,7 +46,7 @@ type ScanState interface {
 // Scanner is implemented by any value that has a Scan method, which scans
 // the input for the representation of a value and stores the result in the
 // receiver, which must be a pointer to be useful.  The Scan method is called
-// for any argument to Scan or Scanln that implements it.
+// for any argument to Scan, Scanf, or Scanln that implements it.
 type Scanner interface {
        Scan(state ScanState, verb int) os.Error
 }
@@ -96,7 +98,7 @@ func Sscanf(str string, format string, a ...interface{}) (n int, err os.Error) {
 // 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) {
-       s := newScanState(r, true)
+       s := newScanState(r, true, false)
        n, err = s.doScan(a)
        s.free()
        return
@@ -105,7 +107,7 @@ func Fscan(r io.Reader, a ...interface{}) (n int, err os.Error) {
 // Fscanln is similar to Fscan, but stops scanning at a newline and
 // after the final item there must be a newline or EOF.
 func Fscanln(r io.Reader, a ...interface{}) (n int, err os.Error) {
-       s := newScanState(r, false)
+       s := newScanState(r, false, true)
        n, err = s.doScan(a)
        s.free()
        return
@@ -115,7 +117,7 @@ func Fscanln(r io.Reader, a ...interface{}) (n int, err os.Error) {
 // values into successive arguments as determined by the format.  It
 // returns the number of items successfully parsed.
 func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err os.Error) {
-       s := newScanState(r, false)
+       s := newScanState(r, false, false)
        n, err = s.doScanf(format, a)
        s.free()
        return
@@ -134,6 +136,7 @@ type ss struct {
        rr         io.RuneReader // where to read input
        buf        bytes.Buffer  // token accumulator
        nlIsSpace  bool          // whether newline counts as white space
+       nlIsEnd    bool          // whether newline terminates scan
        peekRune   int           // one-rune lookahead
        prevRune   int           // last rune returned by GetRune
        atEOF      bool          // already read EOF
@@ -149,6 +152,11 @@ func (s *ss) GetRune() (rune int, err os.Error) {
                s.peekRune = -1
                return
        }
+       if s.nlIsEnd && s.prevRune == '\n' {
+               rune = EOF
+               err = os.EOF
+               return
+       }
        rune, _, err = s.rr.ReadRune()
        if err == nil {
                s.prevRune = rune
@@ -300,7 +308,7 @@ func (r *readRune) ReadRune() (rune int, size int, err os.Error) {
 var ssFree = newCache(func() interface{} { return new(ss) })
 
 // Allocate a new ss struct or grab a cached one.
-func newScanState(r io.Reader, nlIsSpace bool) *ss {
+func newScanState(r io.Reader, nlIsSpace, nlIsEnd bool) *ss {
        s := ssFree.get().(*ss)
        if rr, ok := r.(io.RuneReader); ok {
                s.rr = rr
@@ -308,6 +316,8 @@ func newScanState(r io.Reader, nlIsSpace bool) *ss {
                s.rr = &readRune{reader: r}
        }
        s.nlIsSpace = nlIsSpace
+       s.nlIsEnd = nlIsEnd
+       s.prevRune = -1
        s.peekRune = -1
        s.atEOF = false
        s.maxWid = 0
@@ -804,6 +814,9 @@ func (s *ss) scanOne(verb int, field interface{}) {
        if v, ok := field.(Scanner); ok {
                err = v.Scan(s, verb)
                if err != nil {
+                       if err == os.EOF {
+                               err = io.ErrUnexpectedEOF
+                       }
                        s.error(err)
                }
                return
index 478b109238d478159109fe54124b001696809451..f62888365e9463689adb46ec494773578d738a3f 100644 (file)
@@ -673,3 +673,62 @@ func TestUnreadRuneWithBufio(t *testing.T) {
                t.Errorf("expected αb; got %q", a)
        }
 }
+
+type TwoLines string
+
+// Attempt to read two lines into the object.  Scanln should prevent this
+// because it stops at newline; Scan and Scanf should be fine.
+func (t *TwoLines) Scan(state ScanState, verb int) os.Error {
+       chars := make([]int, 0, 100)
+       for nlCount := 0; nlCount < 2; {
+               c, err := state.GetRune()
+               if err != nil {
+                       return err
+               }
+               chars = append(chars, c)
+               if c == '\n' {
+                       nlCount++
+               }
+       }
+       *t = TwoLines(string(chars))
+       return nil
+}
+
+func TestMultiLine(t *testing.T) {
+       input := "abc\ndef\n"
+       // Sscan should work
+       var tscan TwoLines
+       n, err := Sscan(input, &tscan)
+       if n != 1 {
+               t.Errorf("Sscan: expected 1 item; got %d", n)
+       }
+       if err != nil {
+               t.Errorf("Sscan: expected no error; got %s", err)
+       }
+       if string(tscan) != input {
+               t.Errorf("Sscan: expected %q; got %q", input, tscan)
+       }
+       // Sscanf should work
+       var tscanf TwoLines
+       n, err = Sscanf(input, "%s", &tscanf)
+       if n != 1 {
+               t.Errorf("Sscanf: expected 1 item; got %d", n)
+       }
+       if err != nil {
+               t.Errorf("Sscanf: expected no error; got %s", err)
+       }
+       if string(tscanf) != input {
+               t.Errorf("Sscanf: expected %q; got %q", input, tscanf)
+       }
+       // Sscanln should not work
+       var tscanln TwoLines
+       n, err = Sscanln(input, &tscanln)
+       if n != 0 {
+               t.Errorf("Sscanln: expected 0 items; got %d: %q", n, tscanln)
+       }
+       if err == nil {
+               t.Error("Sscanln: expected error; got none")
+       } else if err != io.ErrUnexpectedEOF {
+               t.Errorf("Sscanln: expected io.ErrUnexpectedEOF (ha!); got %s", err)
+       }
+}