]> Cypherpunks repositories - gostls13.git/commitdiff
big: make Int and Rat implement fmt.Scanner
authorEvan Shaw <chickencha@gmail.com>
Fri, 27 May 2011 22:51:00 +0000 (15:51 -0700)
committerRobert Griesemer <gri@golang.org>
Fri, 27 May 2011 22:51:00 +0000 (15:51 -0700)
R=gri
CC=golang-dev
https://golang.org/cl/4552056

src/pkg/big/int.go
src/pkg/big/int_test.go
src/pkg/big/nat.go
src/pkg/big/nat_test.go
src/pkg/big/rat.go
src/pkg/big/rat_test.go

index 74fbef48d06635eda9c2b7496392023df7e8eebc..b96c387bf460c913bf24ac82c6c0f06560d37e22 100755 (executable)
@@ -10,6 +10,7 @@ import (
        "fmt"
        "os"
        "rand"
+       "strings"
 )
 
 // An Int represents a signed multi-precision integer.
@@ -325,7 +326,7 @@ func charset(ch int) string {
                return lowercaseDigits[0:2]
        case 'o':
                return lowercaseDigits[0:8]
-       case 'd', 'v':
+       case 'd', 's', 'v':
                return lowercaseDigits[0:10]
        case 'x':
                return lowercaseDigits[0:16]
@@ -374,6 +375,49 @@ func (x *Int) Format(s fmt.State, ch int) {
 }
 
 
+// Scan is a support routine for fmt.Scanner. It accepts the formats
+// 'b' (binary), 'o' (octal), 'd' (decimal), 'x' (lowercase hexadecimal),
+// and 'X' (uppercase hexadecimal).
+func (x *Int) Scan(s fmt.ScanState, ch int) os.Error {
+       var base int
+       switch ch {
+       case 'b':
+               base = 2
+       case 'o':
+               base = 8
+       case 'd':
+               base = 10
+       case 'x', 'X':
+               base = 16
+       case 's', 'v':
+               // let scan determine the base
+       default:
+               return os.ErrorString("Int.Scan: invalid verb")
+       }
+
+       ch, _, err := s.ReadRune()
+       if err != nil {
+               return err
+       }
+       neg := false
+       switch ch {
+       case '-':
+               neg = true
+       case '+': // nothing to do
+       default:
+               s.UnreadRune()
+       }
+
+       x.abs, _, err = x.abs.scan(s, base)
+       if err != nil {
+               return err
+       }
+       x.neg = len(x.abs) > 0 && neg // 0 has no sign
+
+       return nil
+}
+
+
 // Int64 returns the int64 representation of z.
 // If z cannot be represented in an int64, the result is undefined.
 func (x *Int) Int64() int64 {
@@ -401,26 +445,27 @@ func (x *Int) Int64() int64 {
 // base 2. Otherwise the selected base is 10.
 //
 func (z *Int) SetString(s string, base int) (*Int, bool) {
-       if len(s) == 0 || base < 0 || base == 1 || 16 < base {
-               return z, false
-       }
-
-       neg := s[0] == '-'
-       if neg || s[0] == '+' {
-               s = s[1:]
-               if len(s) == 0 {
-                       return z, false
+       neg := false
+       if len(s) > 0 {
+               switch s[0] {
+               case '-':
+                       neg = true
+                       fallthrough
+               case '+':
+                       s = s[1:]
                }
        }
 
-       var scanned int
-       z.abs, _, scanned = z.abs.scan(s, base)
-       if scanned != len(s) {
+       r := strings.NewReader(s)
+       abs, _, err := z.abs.scan(r, base)
+       if err != nil {
                return z, false
        }
-       z.neg = len(z.abs) > 0 && neg // 0 has no sign
+       _, _, err = r.ReadRune()
 
-       return z, true
+       z.abs = abs
+       z.neg = len(abs) > 0 && neg // 0 has no sign
+       return z, err == os.EOF     // err == os.EOF => scan consumed all of s
 }
 
 
@@ -784,11 +829,11 @@ func (z *Int) GobEncode() ([]byte, os.Error) {
 // GobDecode implements the gob.GobDecoder interface.
 func (z *Int) GobDecode(buf []byte) os.Error {
        if len(buf) == 0 {
-               return os.NewError("Int.GobDecode: no data")
+               return os.ErrorString("Int.GobDecode: no data")
        }
        b := buf[0]
        if b>>1 != version {
-               return os.NewError(fmt.Sprintf("Int.GobDecode: encoding version %d not supported", b>>1))
+               return os.ErrorString(fmt.Sprintf("Int.GobDecode: encoding version %d not supported", b>>1))
        }
        z.neg = b&1 != 0
        z.abs = z.abs.setBytes(buf[1:])
index 595f04956cb0853c74c303e9386504f49c603cb3..1a492925b858945a1bd7041372226c68617e48a5 100755 (executable)
@@ -397,6 +397,49 @@ func TestFormat(t *testing.T) {
 }
 
 
+var scanTests = []struct {
+       input     string
+       format    string
+       output    string
+       remaining int
+}{
+       {"1010", "%b", "10", 0},
+       {"0b1010", "%v", "10", 0},
+       {"12", "%o", "10", 0},
+       {"012", "%v", "10", 0},
+       {"10", "%d", "10", 0},
+       {"10", "%v", "10", 0},
+       {"a", "%x", "10", 0},
+       {"0xa", "%v", "10", 0},
+       {"A", "%X", "10", 0},
+       {"-A", "%X", "-10", 0},
+       {"+0b1011001", "%v", "89", 0},
+       {"0xA", "%v", "10", 0},
+       {"0 ", "%v", "0", 1},
+       {"2+3", "%v", "2", 2},
+       {"0XABC 12", "%v", "2748", 3},
+}
+
+
+func TestScan(t *testing.T) {
+       var buf bytes.Buffer
+       for i, test := range scanTests {
+               x := new(Int)
+               buf.Reset()
+               buf.WriteString(test.input)
+               if _, err := fmt.Fscanf(&buf, test.format, x); err != nil {
+                       t.Errorf("#%d error: %s", i, err.String())
+               }
+               if x.String() != test.output {
+                       t.Errorf("#%d got %s; want %s", i, x.String(), test.output)
+               }
+               if buf.Len() != test.remaining {
+                       t.Errorf("#%d got %d bytes remaining; want %d", i, buf.Len(), test.remaining)
+               }
+       }
+}
+
+
 // Examples from the Go Language Spec, section "Arithmetic operators"
 var divisionSignsTests = []struct {
        x, y int64
index c2b95e8a20b327aecaf05a9d27f8bc5c8db9cd2d..eb38750c1686b684a515d4c2b527d78ad070447d 100755 (executable)
@@ -18,7 +18,11 @@ package big
 // These are the building blocks for the operations on signed integers
 // and rationals.
 
-import "rand"
+import (
+       "io"
+       "os"
+       "rand"
+)
 
 
 // An unsigned integer x of the form
@@ -604,68 +608,95 @@ func (x nat) bitLen() int {
 }
 
 
-func hexValue(ch byte) int {
-       var d byte
+func hexValue(ch int) int {
+       var d int
        switch {
        case '0' <= ch && ch <= '9':
                d = ch - '0'
-       case 'a' <= ch && ch <= 'f':
+       case 'a' <= ch && ch <= 'z':
                d = ch - 'a' + 10
-       case 'A' <= ch && ch <= 'F':
+       case 'A' <= ch && ch <= 'Z':
                d = ch - 'A' + 10
        default:
                return -1
        }
-       return int(d)
+       return d
 }
 
 
-// scan returns the natural number corresponding to the
-// longest possible prefix of s representing a natural number in a
-// given conversion base, the actual conversion base used, and the
-// prefix length. The syntax of natural numbers follows the syntax
-// of unsigned integer literals in Go.
+// scan returns the natural number corresponding to the longest
+// possible prefix read from r representing a natural number in a
+// given conversion base, the actual conversion base used, and an
+// error, if any. The syntax of natural numbers follows the syntax of
+// unsigned integer literals in Go.
 //
 // If the base argument is 0, the string prefix determines the actual
 // conversion base. A prefix of ``0x'' or ``0X'' selects base 16; the
 // ``0'' prefix selects base 8, and a ``0b'' or ``0B'' prefix selects
 // base 2. Otherwise the selected base is 10.
 //
-func (z nat) scan(s string, base int) (nat, int, int) {
+func (z nat) scan(r io.RuneScanner, base int) (nat, int, os.Error) {
+       n := 0
+       ch, _, err := r.ReadRune()
+       if err != nil {
+               return z, 0, err
+       }
        // determine base if necessary
-       i, n := 0, len(s)
        if base == 0 {
                base = 10
-               if n > 0 && s[0] == '0' {
-                       base, i = 8, 1
-                       if n > 1 {
-                               switch s[1] {
+               if ch == '0' {
+                       n++
+                       switch ch, _, err = r.ReadRune(); err {
+                       case nil:
+                               base = 8
+                               switch ch {
                                case 'x', 'X':
-                                       base, i = 16, 2
+                                       base = 16
                                case 'b', 'B':
-                                       base, i = 2, 2
+                                       base = 2
                                }
+                               if base == 2 || base == 16 {
+                                       n--
+                                       if ch, _, err = r.ReadRune(); err != nil {
+                                               return z, 0, os.ErrorString("syntax error scanning binary or hexadecimal number")
+                                       }
+                               }
+                       case os.EOF:
+                               return z, 10, nil
+                       default:
+                               return z, 0, err
                        }
                }
        }
 
-       // reject illegal bases or strings consisting only of prefix
-       if base < 2 || 16 < base || (base != 8 && i >= n) {
-               return z, 0, 0
+       // reject illegal bases
+       if base < 2 || 'z'-'a'+10 < base {
+               return z, 0, os.ErrorString("illegal number base")
        }
 
        // convert string
        z = z.make(0)
-       for ; i < n; i++ {
-               d := hexValue(s[i])
+       for {
+               d := hexValue(ch)
                if 0 <= d && d < base {
                        z = z.mulAddWW(z, Word(base), Word(d))
                } else {
-                       break
+                       r.UnreadRune()
+                       if n > 0 {
+                               break
+                       }
+                       return z, 0, os.ErrorString("syntax error scanning number")
+               }
+               n++
+               if ch, _, err = r.ReadRune(); err != nil {
+                       if err == os.EOF {
+                               break
+                       }
+                       return z, 0, err
                }
        }
 
-       return z.norm(), base, i
+       return z.norm(), base, nil
 }
 
 
index a29843a3fa25224d34fcb0d94574fdec6f2a4449..25947adda1638eca712a0d45247c2bde0f073d00 100755 (executable)
@@ -4,7 +4,10 @@
 
 package big
 
-import "testing"
+import (
+       "strings"
+       "testing"
+)
 
 var cmpTests = []struct {
        x, y nat
@@ -180,6 +183,8 @@ var strTests = []struct {
        {nat{1234567890}, uppercaseDigits[0:10], "1234567890"},
        {nat{0xdeadbeef}, lowercaseDigits[0:16], "deadbeef"},
        {nat{0xdeadbeef}, uppercaseDigits[0:16], "DEADBEEF"},
+       {nat{0x229be7}, lowercaseDigits[0:17], "1a2b3c"},
+       {nat{0x309663e6}, uppercaseDigits[0:32], "O9COV6"},
 }
 
 
@@ -190,15 +195,58 @@ func TestString(t *testing.T) {
                        t.Errorf("string%+v\n\tgot s = %s; want %s", a, s, a.s)
                }
 
-               x, b, n := nat(nil).scan(a.s, len(a.c))
+               x, b, err := nat(nil).scan(strings.NewReader(a.s), len(a.c))
                if x.cmp(a.x) != 0 {
                        t.Errorf("scan%+v\n\tgot z = %v; want %v", a, x, a.x)
                }
                if b != len(a.c) {
                        t.Errorf("scan%+v\n\tgot b = %d; want %d", a, b, len(a.c))
                }
-               if n != len(a.s) {
-                       t.Errorf("scan%+v\n\tgot n = %d; want %d", a, n, len(a.s))
+               if err != nil {
+                       t.Errorf("scan%+v\n\tgot error = %s", a, err)
+               }
+       }
+}
+
+
+var natScanTests = []struct {
+       s    string // string to be scanned
+       x    nat    // expected nat
+       base int    // expected base
+       ok   bool   // expected success
+}{
+       {s: ""},
+       {"0", nil, 10, true},
+       {"0 ", nil, 8, true},
+       {s: "0x"},
+       {"08", nil, 8, true},
+       {"0b1", nat{1}, 2, true},
+       {"0b11000101", nat{0xc5}, 2, true},
+       {"03271", nat{03271}, 8, true},
+       {"10ab", nat{10}, 10, true},
+       {"1234567890", nat{1234567890}, 10, true},
+       {"0xdeadbeef", nat{0xdeadbeef}, 16, true},
+       {"0XDEADBEEF", nat{0xdeadbeef}, 16, true},
+}
+
+
+func TestScanBase0(t *testing.T) {
+       for _, a := range natScanTests {
+               x, b, err := nat(nil).scan(strings.NewReader(a.s), 0)
+               if err == nil && !a.ok {
+                       t.Errorf("scan%+v\n\texpected error", a)
+               }
+               if err != nil {
+                       if a.ok {
+                               t.Errorf("scan%+v\n\tgot error = %s", a, err)
+                       }
+                       continue
+               }
+               if x.cmp(a.x) != 0 {
+                       t.Errorf("scan%+v\n\tgot z = %v; want %v", a, x, a.x)
+               }
+               if b != a.base {
+                       t.Errorf("scan%+v\n\tgot b = %d; want %d", a, b, a.base)
                }
        }
 }
@@ -344,14 +392,14 @@ var expNNTests = []struct {
 
 func TestExpNN(t *testing.T) {
        for i, test := range expNNTests {
-               x, _, _ := nat(nil).scan(test.x, 0)
-               y, _, _ := nat(nil).scan(test.y, 0)
-               out, _, _ := nat(nil).scan(test.out, 0)
+               x, _, _ := nat(nil).scan(strings.NewReader(test.x), 0)
+               y, _, _ := nat(nil).scan(strings.NewReader(test.y), 0)
+               out, _, _ := nat(nil).scan(strings.NewReader(test.out), 0)
 
                var m nat
 
                if len(test.m) > 0 {
-                       m, _, _ = nat(nil).scan(test.m, 0)
+                       m, _, _ = nat(nil).scan(strings.NewReader(test.m), 0)
                }
 
                z := nat(nil).expNN(x, y, m)
index 2adf316e648182d24432842455e9daf38e0d9370..f11c27425ce8704b21902e9d3becd4d5c49d9ed6 100644 (file)
@@ -6,7 +6,11 @@
 
 package big
 
-import "strings"
+import (
+       "fmt"
+       "os"
+       "strings"
+)
 
 // A Rat represents a quotient a/b of arbitrary precision. The zero value for
 // a Rat, 0/0, is not a legal Rat.
@@ -209,6 +213,28 @@ func (z *Rat) Set(x *Rat) *Rat {
 }
 
 
+func ratTok(ch int) bool {
+       return strings.IndexRune("+-/0123456789.eE", ch) >= 0
+}
+
+
+// Scan is a support routine for fmt.Scanner. It accepts the formats
+// 'e', 'E', 'f', 'F', 'g', 'G', and 'v'. All formats are equivalent.
+func (z *Rat) Scan(s fmt.ScanState, ch int) os.Error {
+       tok, err := s.Token(true, ratTok)
+       if err != nil {
+               return err
+       }
+       if strings.IndexRune("efgEFGv", ch) < 0 {
+               return os.ErrorString("Rat.Scan: invalid verb")
+       }
+       if _, ok := z.SetString(string(tok)); !ok {
+               return os.ErrorString("Rat.Scan: invalid syntax")
+       }
+       return nil
+}
+
+
 // SetString sets z to the value of s and returns z and a boolean indicating
 // success. s can be given as a fraction "a/b" or as a floating-point number
 // optionally followed by an exponent. If the operation failed, the value of z
@@ -225,8 +251,8 @@ func (z *Rat) SetString(s string) (*Rat, bool) {
                        return z, false
                }
                s = s[sep+1:]
-               var n int
-               if z.b, _, n = z.b.scan(s, 10); n != len(s) {
+               var err os.Error
+               if z.b, _, err = z.b.scan(strings.NewReader(s), 10); err != nil {
                        return z, false
                }
                return z.norm(), true
index 8f42949b087e3ebd6b5dc61886500cd60f0ead06..ae5c7c9936384bc7ca29ca0a8f98aa82aa5092fd 100644 (file)
@@ -4,7 +4,11 @@
 
 package big
 
-import "testing"
+import (
+       "bytes"
+       "fmt"
+       "testing"
+)
 
 
 var setStringTests = []struct {
@@ -53,6 +57,29 @@ func TestRatSetString(t *testing.T) {
 }
 
 
+func TestRatScan(t *testing.T) {
+       var buf bytes.Buffer
+       for i, test := range setStringTests {
+               x := new(Rat)
+               buf.Reset()
+               buf.WriteString(test.in)
+
+               _, err := fmt.Fscanf(&buf, "%v", x)
+               if err == nil != test.ok {
+                       if test.ok {
+                               t.Errorf("#%d error: %s", i, err.String())
+                       } else {
+                               t.Errorf("#%d expected error", i)
+                       }
+                       continue
+               }
+               if err == nil && x.RatString() != test.out {
+                       t.Errorf("#%d got %s want %s", i, x.RatString(), test.out)
+               }
+       }
+}
+
+
 var floatStringTests = []struct {
        in   string
        prec int