// a digit count, and a read or syntax error err, if any.
//
// number = [ prefix ] mantissa .
-// prefix = "0" [ "x" | "X" | "b" | "B" ] .
+// prefix = "0" [ "b" | "B" | "o" | "O" | "x" | "X" ] .
// mantissa = digits | digits "." [ digits ] | "." digits .
// digits = digit { digit } .
// digit = "0" ... "9" | "a" ... "z" | "A" ... "Z" .
//
// Unless fracOk is set, the base argument must be 0 or a value between
// 2 and MaxBase. If fracOk is set, the base argument must be one of
-// 0, 2, 10, or 16. Providing an invalid base argument leads to a run-
+// 0, 2, 8, 10, or 16. Providing an invalid base argument leads to a run-
// time panic.
//
// For base 0, the number prefix determines the actual base: A prefix of
-// ``0x'' or ``0X'' selects base 16; if fracOk is not set, the ``0'' prefix
-// selects base 8, and a ``0b'' or ``0B'' prefix selects base 2. Otherwise
+// ``0b'' or ``0B'' selects base 2, ``0o'' or ``0O'' selects base 8, and
+// ``0x'' or ``0X'' selects base 16. If fracOk is false, a ``0'' prefix
+// (immediately followed by digits) selects base 8 as well. Otherwise,
// the selected base is 10 and no prefix is accepted.
//
-// If fracOk is set, an octal prefix is ignored (a leading ``0'' simply
-// stands for a zero digit), and a period followed by a fractional part
-// is permitted. The result value is computed as if there were no period
-// present; and the count value is used to determine the fractional part.
+// If fracOk is set, a period followed by a fractional part is permitted.
+// The result value is computed as if there were no period present; and
+// the count value is used to determine the fractional part.
//
// For bases <= 36, lower and upper case letters are considered the same:
// The letters 'a' to 'z' and 'A' to 'Z' represent digit values 10 to 35.
// reject illegal bases
baseOk := base == 0 ||
!fracOk && 2 <= base && base <= MaxBase ||
- fracOk && (base == 2 || base == 10 || base == 16)
+ fracOk && (base == 2 || base == 8 || base == 10 || base == 16)
if !baseOk {
panic(fmt.Sprintf("illegal number base %d", base))
}
// one char look-ahead
ch, err := r.ReadByte()
if err != nil {
- return
+ return // io.EOF is also an error in this case
}
// determine actual base
- b = base
+ b, prefix := base, 0
if base == 0 {
// actual base is 10 unless there's a base prefix
b = 10
if ch == '0' {
count = 1
- switch ch, err = r.ReadByte(); err {
- case nil:
- // possibly one of 0x, 0X, 0b, 0B
- if !fracOk {
- b = 8
+ ch, err = r.ReadByte()
+ if err != nil {
+ if err == io.EOF {
+ err = nil // not an error; input is "0"
+ res = z[:0]
}
- switch ch {
- case 'x', 'X':
- b = 16
- case 'b', 'B':
- b = 2
+ return
+ }
+ // possibly one of 0b, 0B, 0o, 0O, 0x, 0X
+ switch ch {
+ case 'b', 'B':
+ b, prefix = 2, 'b'
+ case 'o', 'O':
+ b, prefix = 8, 'o'
+ case 'x', 'X':
+ b, prefix = 16, 'x'
+ default:
+ if !fracOk {
+ b, prefix = 8, '0'
}
- switch b {
- case 16, 2:
- count = 0 // prefix is not counted
+ }
+ if prefix != 0 {
+ count = 0 // prefix is not counted
+ if prefix != '0' {
if ch, err = r.ReadByte(); err != nil {
- // io.EOF is also an error in this case
- return
+ return // io.EOF is also an error in this case
}
- case 8:
- count = 0 // prefix is not counted
}
- case io.EOF:
- // input is "0"
- res = z[:0]
- err = nil
- return
- default:
- // read error
- return
}
}
}
if count == 0 {
// no digits found
- switch {
- case base == 0 && b == 8:
+ if prefix == '0' {
// there was only the octal prefix 0 (possibly followed by digits > 7);
// count as one digit and return base 10, not 8
count = 1
b = 10
- case base != 0 || b != 8:
- // there was neither a mantissa digit nor the octal prefix 0
+ } else {
err = errors.New("syntax error scanning number")
}
return
ok bool // expected success
next rune // next character (or 0, if at EOF)
}{
- // error: no mantissa
+ // invalid: no mantissa
{},
{s: "?"},
{base: 10},
{base: 36},
{base: 62},
{s: "?", base: 10},
+ {s: "0b"},
+ {s: "0o"},
{s: "0x"},
+ {s: "0b2"},
+ {s: "0B2"},
+ {s: "0o8"},
+ {s: "0O8"},
+ {s: "0xg"},
+ {s: "0Xg"},
{s: "345", base: 2},
- // error: incorrect use of decimal point
+ // invalid: incorrect use of decimal point
{s: ".0"},
{s: ".0", base: 10},
{s: ".", base: 0},
{s: "0x.0"},
- // no errors
+ // valid, no decimal point
{"0", 0, false, nil, 10, 1, true, 0},
{"0", 10, false, nil, 10, 1, true, 0},
{"0", 36, false, nil, 36, 1, true, 0},
{"1", 0, false, nat{1}, 10, 1, true, 0},
{"1", 10, false, nat{1}, 10, 1, true, 0},
{"0 ", 0, false, nil, 10, 1, true, ' '},
+ {"00 ", 0, false, nil, 8, 1, true, ' '}, // octal 0
+ {"0b1", 0, false, nat{1}, 2, 1, true, 0},
+ {"0B11000101", 0, false, nat{0xc5}, 2, 8, true, 0},
+ {"0B110001012", 0, false, nat{0xc5}, 2, 8, true, '2'},
+ {"07", 0, false, nat{7}, 8, 1, true, 0},
{"08", 0, false, nil, 10, 1, true, '8'},
{"08", 10, false, nat{8}, 10, 2, true, 0},
{"018", 0, false, nat{1}, 8, 1, true, '8'},
- {"0b1", 0, false, nat{1}, 2, 1, true, 0},
- {"0b11000101", 0, false, nat{0xc5}, 2, 8, true, 0},
+ {"0o7", 0, false, nat{7}, 8, 1, true, 0},
+ {"0o18", 0, false, nat{1}, 8, 1, true, '8'},
+ {"0O17", 0, false, nat{017}, 8, 2, true, 0},
{"03271", 0, false, nat{03271}, 8, 4, true, 0},
{"10ab", 0, false, nat{10}, 10, 2, true, 'a'},
{"1234567890", 0, false, nat{1234567890}, 10, 10, true, 0},
{"0xdeadbeef", 0, false, nat{0xdeadbeef}, 16, 8, true, 0},
{"0XDEADBEEF", 0, false, nat{0xdeadbeef}, 16, 8, true, 0},
- // no errors, decimal point
+ // valid, with decimal point
{"0.", 0, false, nil, 10, 1, true, '.'},
{"0.", 10, true, nil, 10, 0, true, 0},
{"0.1.2", 10, true, nat{1}, 10, -1, true, '.'},
{".000", 10, true, nil, 10, -3, true, 0},
{"12.3", 10, true, nat{123}, 10, -1, true, 0},
{"012.345", 10, true, nat{12345}, 10, -3, true, 0},
+ {"0.1", 0, true, nat{1}, 10, -1, true, 0},
+ {"0.1", 2, true, nat{1}, 2, -1, true, 0},
+ {"0.12", 2, true, nat{1}, 2, -1, true, '2'},
+ {"0b0.1", 0, true, nat{1}, 2, -1, true, 0},
+ {"0B0.12", 0, true, nat{1}, 2, -1, true, '2'},
+ {"0o0.7", 0, true, nat{7}, 8, -1, true, 0},
+ {"0O0.78", 0, true, nat{7}, 8, -1, true, '8'},
}
func TestScanBase(t *testing.T) {