internal/format: move fmtFlags to internal/format
And rename it to Parser.
Also export fields that need to be exported.
Change-Id: Iacc8b783a6756cd876caf6e3dd3d4c90b57209c6
Reviewed-on: https://go-review.googlesource.com/79236
Run-TryBot: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/internal/format/parser.go b/internal/format/parser.go
new file mode 100644
index 0000000..68a8e8e
--- /dev/null
+++ b/internal/format/parser.go
@@ -0,0 +1,352 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package format
+
+import (
+ "reflect"
+ "unicode/utf8"
+)
+
+// A Parser parses a format string. The result from the parse are set in the
+// struct fields.
+type Parser struct {
+ Verb rune
+
+ WidthPresent bool
+ PrecPresent bool
+ Minus bool
+ Plus bool
+ Sharp bool
+ Space bool
+ Zero bool
+
+ // For the formats %+v %#v, we set the plusV/sharpV flags
+ // and clear the plus/sharp flags since %+v and %#v are in effect
+ // different, flagless formats set at the top level.
+ PlusV bool
+ SharpV bool
+
+ Width int
+ Prec int // precision
+
+ // retain arguments across calls.
+ Args []interface{}
+ // retain current argument number across calls
+ ArgNum int
+
+ // reordered records whether the format string used argument reordering.
+ Reordered bool
+ // goodArgNum records whether the most recent reordering directive was valid.
+ goodArgNum bool
+
+ // position info
+ format string
+ startPos int
+ endPos int
+ Status Status
+}
+
+// Reset initializes a parser to scan format strings for the given args.
+func (p *Parser) Reset(args []interface{}) {
+ p.Args = args
+ p.ArgNum = 0
+ p.startPos = 0
+ p.Reordered = false
+}
+
+// Text returns the part of the format string that was parsed by the last call
+// to Scan. It returns the original substitution clause if the current scan
+// parsed a substitution.
+func (p *Parser) Text() string { return p.format[p.startPos:p.endPos] }
+
+// SetFormat sets a new format string to parse. It does not reset the argument
+// count.
+func (p *Parser) SetFormat(format string) {
+ p.format = format
+ p.startPos = 0
+ p.endPos = 0
+}
+
+// Status indicates the result type of a call to Scan.
+type Status int
+
+const (
+ StatusText Status = iota
+ StatusSubstitution
+ StatusBadWidthSubstitution
+ StatusBadPrecSubstitution
+ StatusNoVerb
+ StatusBadArgNum
+ StatusMissingArg
+)
+
+// ClearFlags reset the parser to default behavior.
+func (p *Parser) ClearFlags() {
+ p.WidthPresent = false
+ p.PrecPresent = false
+ p.Minus = false
+ p.Plus = false
+ p.Sharp = false
+ p.Space = false
+ p.Zero = false
+
+ p.PlusV = false
+ p.SharpV = false
+}
+
+// Scan scans the next part of the format string and sets the status to
+// indicate whether it scanned a string literal, substitution or error.
+func (p *Parser) Scan() bool {
+ p.Status = StatusText
+ format := p.format
+ end := len(format)
+ if p.endPos >= end {
+ return false
+ }
+ afterIndex := false // previous item in format was an index like [3].
+
+ p.startPos = p.endPos
+ p.goodArgNum = true
+ i := p.startPos
+ for i < end && format[i] != '%' {
+ i++
+ }
+ if i > p.startPos {
+ p.endPos = i
+ return true
+ }
+ // Process one verb
+ i++
+
+ p.Status = StatusSubstitution
+
+ // Do we have flags?
+ p.ClearFlags()
+
+simpleFormat:
+ for ; i < end; i++ {
+ c := p.format[i]
+ switch c {
+ case '#':
+ p.Sharp = true
+ case '0':
+ p.Zero = !p.Minus // Only allow zero padding to the left.
+ case '+':
+ p.Plus = true
+ case '-':
+ p.Minus = true
+ p.Zero = false // Do not pad with zeros to the right.
+ case ' ':
+ p.Space = true
+ default:
+ // Fast path for common case of ascii lower case simple verbs
+ // without precision or width or argument indices.
+ if 'a' <= c && c <= 'z' && p.ArgNum < len(p.Args) {
+ if c == 'v' {
+ // Go syntax
+ p.SharpV = p.Sharp
+ p.Sharp = false
+ // Struct-field syntax
+ p.PlusV = p.Plus
+ p.Plus = false
+ }
+ p.Verb = rune(c)
+ p.ArgNum++
+ p.endPos = i + 1
+ return true
+ }
+ // Format is more complex than simple flags and a verb or is malformed.
+ break simpleFormat
+ }
+ }
+
+ // Do we have an explicit argument index?
+ i, afterIndex = p.updateArgNumber(format, i)
+
+ // Do we have width?
+ if i < end && format[i] == '*' {
+ i++
+ p.Width, p.WidthPresent = p.intFromArg()
+
+ if !p.WidthPresent {
+ p.Status = StatusBadWidthSubstitution
+ }
+
+ // We have a negative width, so take its value and ensure
+ // that the minus flag is set
+ if p.Width < 0 {
+ p.Width = -p.Width
+ p.Minus = true
+ p.Zero = false // Do not pad with zeros to the right.
+ }
+ afterIndex = false
+ } else {
+ p.Width, p.WidthPresent, i = parsenum(format, i, end)
+ if afterIndex && p.WidthPresent { // "%[3]2d"
+ p.goodArgNum = false
+ }
+ }
+
+ // Do we have precision?
+ if i+1 < end && format[i] == '.' {
+ i++
+ if afterIndex { // "%[3].2d"
+ p.goodArgNum = false
+ }
+ i, afterIndex = p.updateArgNumber(format, i)
+ if i < end && format[i] == '*' {
+ i++
+ p.Prec, p.PrecPresent = p.intFromArg()
+ // Negative precision arguments don't make sense
+ if p.Prec < 0 {
+ p.Prec = 0
+ p.PrecPresent = false
+ }
+ if !p.PrecPresent {
+ p.Status = StatusBadPrecSubstitution
+ }
+ afterIndex = false
+ } else {
+ p.Prec, p.PrecPresent, i = parsenum(format, i, end)
+ if !p.PrecPresent {
+ p.Prec = 0
+ p.PrecPresent = true
+ }
+ }
+ }
+
+ if !afterIndex {
+ i, afterIndex = p.updateArgNumber(format, i)
+ }
+
+ if i >= end {
+ p.endPos = i
+ p.Status = StatusNoVerb
+ return true
+ }
+
+ verb, w := utf8.DecodeRuneInString(format[i:])
+ p.endPos = i + w
+ p.Verb = verb
+
+ switch {
+ case verb == '%': // Percent does not absorb operands and ignores f.wid and f.prec.
+ p.startPos = p.endPos - 1
+ p.Status = StatusText
+ case !p.goodArgNum:
+ p.Status = StatusBadArgNum
+ case p.ArgNum >= len(p.Args): // No argument left over to print for the current verb.
+ p.Status = StatusMissingArg
+ case verb == 'v':
+ // Go syntax
+ p.SharpV = p.Sharp
+ p.Sharp = false
+ // Struct-field syntax
+ p.PlusV = p.Plus
+ p.Plus = false
+ fallthrough
+ default:
+ p.ArgNum++
+ }
+ return true
+}
+
+// intFromArg gets the ArgNumth element of Args. On return, isInt reports
+// whether the argument has integer type.
+func (p *Parser) intFromArg() (num int, isInt bool) {
+ if p.ArgNum < len(p.Args) {
+ arg := p.Args[p.ArgNum]
+ num, isInt = arg.(int) // Almost always OK.
+ if !isInt {
+ // Work harder.
+ switch v := reflect.ValueOf(arg); v.Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ n := v.Int()
+ if int64(int(n)) == n {
+ num = int(n)
+ isInt = true
+ }
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ n := v.Uint()
+ if int64(n) >= 0 && uint64(int(n)) == n {
+ num = int(n)
+ isInt = true
+ }
+ default:
+ // Already 0, false.
+ }
+ }
+ p.ArgNum++
+ if tooLarge(num) {
+ num = 0
+ isInt = false
+ }
+ }
+ return
+}
+
+// parseArgNumber returns the value of the bracketed number, minus 1
+// (explicit argument numbers are one-indexed but we want zero-indexed).
+// The opening bracket is known to be present at format[0].
+// The returned values are the index, the number of bytes to consume
+// up to the closing paren, if present, and whether the number parsed
+// ok. The bytes to consume will be 1 if no closing paren is present.
+func parseArgNumber(format string) (index int, wid int, ok bool) {
+ // There must be at least 3 bytes: [n].
+ if len(format) < 3 {
+ return 0, 1, false
+ }
+
+ // Find closing bracket.
+ for i := 1; i < len(format); i++ {
+ if format[i] == ']' {
+ width, ok, newi := parsenum(format, 1, i)
+ if !ok || newi != i {
+ return 0, i + 1, false
+ }
+ return width - 1, i + 1, true // arg numbers are one-indexed and skip paren.
+ }
+ }
+ return 0, 1, false
+}
+
+// updateArgNumber returns the next argument to evaluate, which is either the value of the passed-in
+// argNum or the value of the bracketed integer that begins format[i:]. It also returns
+// the new value of i, that is, the index of the next byte of the format to process.
+func (p *Parser) updateArgNumber(format string, i int) (newi int, found bool) {
+ if len(format) <= i || format[i] != '[' {
+ return i, false
+ }
+ p.Reordered = true
+ index, wid, ok := parseArgNumber(format[i:])
+ if ok && 0 <= index && index < len(p.Args) {
+ p.ArgNum = index
+ return i + wid, true
+ }
+ p.goodArgNum = false
+ return i + wid, ok
+}
+
+// tooLarge reports whether the magnitude of the integer is
+// too large to be used as a formatting width or precision.
+func tooLarge(x int) bool {
+ const max int = 1e6
+ return x > max || x < -max
+}
+
+// parsenum converts ASCII to integer. num is 0 (and isnum is false) if no number present.
+func parsenum(s string, start, end int) (num int, isnum bool, newi int) {
+ if start >= end {
+ return 0, false, end
+ }
+ for newi = start; newi < end && '0' <= s[newi] && s[newi] <= '9'; newi++ {
+ if tooLarge(num) {
+ return 0, false, end // Overflow; crazy long number most likely.
+ }
+ num = num*10 + int(s[newi]-'0')
+ isnum = true
+ }
+ return
+}
diff --git a/internal/format/parser_test.go b/internal/format/parser_test.go
new file mode 100644
index 0000000..7229908
--- /dev/null
+++ b/internal/format/parser_test.go
@@ -0,0 +1,32 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package format
+
+import "testing"
+
+// TODO: most of Parser is tested in x/message. Move some tests here.
+
+func TestParsenum(t *testing.T) {
+ testCases := []struct {
+ s string
+ start, end int
+ num int
+ isnum bool
+ newi int
+ }{
+ {"a123", 0, 4, 0, false, 0},
+ {"1234", 1, 1, 0, false, 1},
+ {"123a", 0, 4, 123, true, 3},
+ {"12a3", 0, 4, 12, true, 2},
+ {"1234", 0, 4, 1234, true, 4},
+ {"1a234", 1, 3, 0, false, 1},
+ }
+ for _, tt := range testCases {
+ num, isnum, newi := parsenum(tt.s, tt.start, tt.end)
+ if num != tt.num || isnum != tt.isnum || newi != tt.newi {
+ t.Errorf("parsenum(%q, %d, %d) = %d, %v, %d, want %d, %v, %d", tt.s, tt.start, tt.end, num, isnum, newi, tt.num, tt.isnum, tt.newi)
+ }
+ }
+}
diff --git a/message/fmt_test.go b/message/fmt_test.go
index 45baebe..aea36d8 100755
--- a/message/fmt_test.go
+++ b/message/fmt_test.go
@@ -1869,26 +1869,3 @@
}
}
}
-
-func TestParsenum(t *testing.T) {
- testCases := []struct {
- s string
- start, end int
- num int
- isnum bool
- newi int
- }{
- {"a123", 0, 4, 0, false, 0},
- {"1234", 1, 1, 0, false, 1},
- {"123a", 0, 4, 123, true, 3},
- {"12a3", 0, 4, 12, true, 2},
- {"1234", 0, 4, 1234, true, 4},
- {"1a234", 1, 3, 0, false, 1},
- }
- for _, tt := range testCases {
- num, isnum, newi := parsenum(tt.s, tt.start, tt.end)
- if num != tt.num || isnum != tt.isnum || newi != tt.newi {
- t.Errorf("parsenum(%q, %d, %d) = %d, %v, %d, want %d, %v, %d", tt.s, tt.start, tt.end, num, isnum, newi, tt.num, tt.isnum, tt.newi)
- }
- }
-}
diff --git a/message/format.go b/message/format.go
index d5534ae..9fb4c6b 100644
--- a/message/format.go
+++ b/message/format.go
@@ -8,6 +8,8 @@
"bytes"
"strconv"
"unicode/utf8"
+
+ "golang.org/x/text/internal/format"
)
const (
@@ -20,93 +22,20 @@
unsigned = false
)
-// flags placed in a separate struct for easy clearing.
-type fmtFlags struct {
- verb rune
-
- widPresent bool
- precPresent bool
- minus bool
- plus bool
- sharp bool
- space bool
- zero bool
-
- // For the formats %+v %#v, we set the plusV/sharpV flags
- // and clear the plus/sharp flags since %+v and %#v are in effect
- // different, flagless formats set at the top level.
- plusV bool
- sharpV bool
-
- wid int // width
- prec int // precision
-
- // retain arguments across calls.
- args []interface{}
- // retain current argument number across calls
- argNum int
-
- // reordered records whether the format string used argument reordering.
- reordered bool
- // goodArgNum records whether the most recent reordering directive was valid.
- goodArgNum bool
-
- // position info
- format string
- startPos int
- endPos int
- state state
-}
-
-func (p *fmtFlags) Text() string { return p.format[p.startPos:p.endPos] }
-
-func (p *fmtFlags) init(format string) {
- p.format = format
- p.startPos = 0
- p.endPos = 0
- p.argNum = 0
-}
-
-type state int
-
-const (
- text state = iota
- substitution
- badWidth
- badPrec
- noVerb
- badArgNum
- missingArg
-)
-
// A formatInfo is the raw formatter used by Printf etc.
// It prints into a buffer that must be set up separately.
type formatInfo struct {
buf *bytes.Buffer
- fmtFlags
+ format.Parser
// intbuf is large enough to store %b of an int64 with a sign and
// avoids padding at the end of the struct on 32 bit architectures.
intbuf [68]byte
}
-func (f *fmtFlags) clearflags() {
- f.widPresent = false
- f.precPresent = false
- f.minus = false
- f.plus = false
- f.sharp = false
- f.space = false
- f.zero = false
-
- f.plusV = false
- f.sharpV = false
-}
-
func (f *formatInfo) init(buf *bytes.Buffer) {
f.buf = buf
- f.clearflags()
}
// writePadding generates n bytes of padding.
@@ -117,7 +46,7 @@
f.buf.Grow(n)
// Decide which byte the padding should be filled with.
padByte := byte(' ')
- if f.zero {
+ if f.Zero {
padByte = byte('0')
}
// Fill padding with padByte.
@@ -128,12 +57,12 @@
// pad appends b to f.buf, padded on left (!f.minus) or right (f.minus).
func (f *formatInfo) pad(b []byte) {
- if !f.widPresent || f.wid == 0 {
+ if !f.WidthPresent || f.Width == 0 {
f.buf.Write(b)
return
}
- width := f.wid - utf8.RuneCount(b)
- if !f.minus {
+ width := f.Width - utf8.RuneCount(b)
+ if !f.Minus {
// left padding
f.writePadding(width)
f.buf.Write(b)
@@ -146,12 +75,12 @@
// padString appends s to f.buf, padded on left (!f.minus) or right (f.minus).
func (f *formatInfo) padString(s string) {
- if !f.widPresent || f.wid == 0 {
+ if !f.WidthPresent || f.Width == 0 {
f.buf.WriteString(s)
return
}
- width := f.wid - utf8.RuneCountInString(s)
- if !f.minus {
+ width := f.Width - utf8.RuneCountInString(s)
+ if !f.Minus {
// left padding
f.writePadding(width)
f.buf.WriteString(s)
@@ -179,8 +108,8 @@
// for formatting -1 with %#U ("U+FFFFFFFFFFFFFFFF") which fits
// into the already allocated intbuf with a capacity of 68 bytes.
prec := 4
- if f.precPresent && f.prec > 4 {
- prec = f.prec
+ if f.PrecPresent && f.Prec > 4 {
+ prec = f.Prec
// Compute space needed for "U+" , number, " '", character, "'".
width := 2 + prec + 2 + utf8.UTFMax + 1
if width > len(buf) {
@@ -192,7 +121,7 @@
i := len(buf)
// For %#U we want to add a space and a quoted character at the end of the buffer.
- if f.sharp && u <= utf8.MaxRune && strconv.IsPrint(rune(u)) {
+ if f.Sharp && u <= utf8.MaxRune && strconv.IsPrint(rune(u)) {
i--
buf[i] = '\''
i -= utf8.RuneLen(rune(u))
@@ -224,10 +153,10 @@
i--
buf[i] = 'U'
- oldZero := f.zero
- f.zero = false
+ oldZero := f.Zero
+ f.Zero = false
f.pad(buf[i:])
- f.zero = oldZero
+ f.Zero = oldZero
}
// fmt_integer formats signed and unsigned integers.
@@ -240,9 +169,9 @@
buf := f.intbuf[0:]
// The already allocated f.intbuf with a capacity of 68 bytes
// is large enough for integer formatting when no precision or width is set.
- if f.widPresent || f.precPresent {
+ if f.WidthPresent || f.PrecPresent {
// Account 3 extra bytes for possible addition of a sign and "0x".
- width := 3 + f.wid + f.prec // wid and prec are always positive.
+ width := 3 + f.Width + f.Prec // wid and prec are always positive.
if width > len(buf) {
// We're going to need a bigger boat.
buf = make([]byte, width)
@@ -253,19 +182,19 @@
// If both are specified the f.zero flag is ignored and
// padding with spaces is used instead.
prec := 0
- if f.precPresent {
- prec = f.prec
+ if f.PrecPresent {
+ prec = f.Prec
// Precision of 0 and value of 0 means "print nothing" but padding.
if prec == 0 && u == 0 {
- oldZero := f.zero
- f.zero = false
- f.writePadding(f.wid)
- f.zero = oldZero
+ oldZero := f.Zero
+ f.Zero = false
+ f.writePadding(f.Width)
+ f.Zero = oldZero
return
}
- } else if f.zero && f.widPresent {
- prec = f.wid
- if negative || f.plus || f.space {
+ } else if f.Zero && f.WidthPresent {
+ prec = f.Width
+ if negative || f.Plus || f.Space {
prec-- // leave room for sign
}
}
@@ -313,7 +242,7 @@
}
// Various prefixes: 0x, -, etc.
- if f.sharp {
+ if f.Sharp {
switch base {
case 8:
if buf[i] != '0' {
@@ -332,26 +261,26 @@
if negative {
i--
buf[i] = '-'
- } else if f.plus {
+ } else if f.Plus {
i--
buf[i] = '+'
- } else if f.space {
+ } else if f.Space {
i--
buf[i] = ' '
}
// Left padding with zeros has already been handled like precision earlier
// or the f.zero flag is ignored due to an explicitly set precision.
- oldZero := f.zero
- f.zero = false
+ oldZero := f.Zero
+ f.Zero = false
f.pad(buf[i:])
- f.zero = oldZero
+ f.Zero = oldZero
}
// truncate truncates the string to the specified precision, if present.
func (f *formatInfo) truncate(s string) string {
- if f.precPresent {
- n := f.prec
+ if f.PrecPresent {
+ n := f.Prec
for i := range s {
n--
if n < 0 {
@@ -376,46 +305,46 @@
length = len(s)
}
// Set length to not process more bytes than the precision demands.
- if f.precPresent && f.prec < length {
- length = f.prec
+ if f.PrecPresent && f.Prec < length {
+ length = f.Prec
}
// Compute width of the encoding taking into account the f.sharp and f.space flag.
width := 2 * length
if width > 0 {
- if f.space {
+ if f.Space {
// Each element encoded by two hexadecimals will get a leading 0x or 0X.
- if f.sharp {
+ if f.Sharp {
width *= 2
}
// Elements will be separated by a space.
width += length - 1
- } else if f.sharp {
+ } else if f.Sharp {
// Only a leading 0x or 0X will be added for the whole string.
width += 2
}
} else { // The byte slice or string that should be encoded is empty.
- if f.widPresent {
- f.writePadding(f.wid)
+ if f.WidthPresent {
+ f.writePadding(f.Width)
}
return
}
// Handle padding to the left.
- if f.widPresent && f.wid > width && !f.minus {
- f.writePadding(f.wid - width)
+ if f.WidthPresent && f.Width > width && !f.Minus {
+ f.writePadding(f.Width - width)
}
// Write the encoding directly into the output buffer.
buf := f.buf
- if f.sharp {
+ if f.Sharp {
// Add leading 0x or 0X.
buf.WriteByte('0')
buf.WriteByte(digits[16])
}
var c byte
for i := 0; i < length; i++ {
- if f.space && i > 0 {
+ if f.Space && i > 0 {
// Separate elements with a space.
buf.WriteByte(' ')
- if f.sharp {
+ if f.Sharp {
// Add leading 0x or 0X for each element.
buf.WriteByte('0')
buf.WriteByte(digits[16])
@@ -431,8 +360,8 @@
buf.WriteByte(digits[c&0xF])
}
// Handle padding to the right.
- if f.widPresent && f.wid > width && f.minus {
- f.writePadding(f.wid - width)
+ if f.WidthPresent && f.Width > width && f.Minus {
+ f.writePadding(f.Width - width)
}
}
@@ -451,12 +380,12 @@
// if the string does not contain any control characters other than tab.
func (f *formatInfo) fmt_q(s string) {
s = f.truncate(s)
- if f.sharp && strconv.CanBackquote(s) {
+ if f.Sharp && strconv.CanBackquote(s) {
f.padString("`" + s + "`")
return
}
buf := f.intbuf[:0]
- if f.plus {
+ if f.Plus {
f.pad(strconv.AppendQuoteToASCII(buf, s))
} else {
f.pad(strconv.AppendQuote(buf, s))
@@ -483,7 +412,7 @@
r = utf8.RuneError
}
buf := f.intbuf[:0]
- if f.plus {
+ if f.Plus {
f.pad(strconv.AppendQuoteRuneToASCII(buf, r))
} else {
f.pad(strconv.AppendQuoteRune(buf, r))
@@ -494,8 +423,8 @@
// for strconv.AppendFloat and therefore fits into a byte.
func (f *formatInfo) fmt_float(v float64, size int, verb rune, prec int) {
// Explicit precision in format specifier overrules default precision.
- if f.precPresent {
- prec = f.prec
+ if f.PrecPresent {
+ prec = f.Prec
}
// Format number, reserving space for leading + sign if needed.
num := strconv.AppendFloat(f.intbuf[:1], v, byte(verb), prec, size)
@@ -506,25 +435,25 @@
}
// f.space means to add a leading space instead of a "+" sign unless
// the sign is explicitly asked for by f.plus.
- if f.space && num[0] == '+' && !f.plus {
+ if f.Space && num[0] == '+' && !f.Plus {
num[0] = ' '
}
// Special handling for infinities and NaN,
// which don't look like a number so shouldn't be padded with zeros.
if num[1] == 'I' || num[1] == 'N' {
- oldZero := f.zero
- f.zero = false
+ oldZero := f.Zero
+ f.Zero = false
// Remove sign before NaN if not asked for.
- if num[1] == 'N' && !f.space && !f.plus {
+ if num[1] == 'N' && !f.Space && !f.Plus {
num = num[1:]
}
f.pad(num)
- f.zero = oldZero
+ f.Zero = oldZero
return
}
// The sharp flag forces printing a decimal point for non-binary formats
// and retains trailing zeros, which we may need to restore.
- if f.sharp && verb != 'b' {
+ if f.Sharp && verb != 'b' {
digits := 0
switch verb {
case 'v', 'g', 'G':
@@ -563,12 +492,12 @@
num = append(num, tail...)
}
// We want a sign if asked for and if the sign is not positive.
- if f.plus || num[0] != '+' {
+ if f.Plus || num[0] != '+' {
// If we're zero padding to the left we want the sign before the leading zeros.
// Achieve this by writing the sign out and then padding the unsigned number.
- if f.zero && f.widPresent && f.wid > len(num) {
+ if f.Zero && f.WidthPresent && f.Width > len(num) {
f.buf.WriteByte(num[0])
- f.writePadding(f.wid - len(num))
+ f.writePadding(f.Width - len(num))
f.buf.Write(num[1:])
return
}
diff --git a/message/message.go b/message/message.go
index db80e47..8a84d0d 100644
--- a/message/message.go
+++ b/message/message.go
@@ -120,7 +120,7 @@
func lookupAndFormat(p *Printer, r Reference, a []interface{}) {
p.printer.reset()
- p.printer.fmt.args = a
+ p.printer.fmt.Reset(a)
var id, msg string
switch v := r.(type) {
case string:
@@ -142,8 +142,8 @@
// Arg implements catmsg.Renderer.
func (p *printer) Arg(i int) interface{} { // TODO, also return "ok" bool
i--
- if uint(i) < uint(len(p.fmt.args)) {
- return p.fmt.args[i]
+ if uint(i) < uint(len(p.fmt.Args)) {
+ return p.fmt.Args[i]
}
return nil
}
diff --git a/message/print.go b/message/print.go
index 4d80d51..5a55d41 100644
--- a/message/print.go
+++ b/message/print.go
@@ -67,9 +67,6 @@
func (p *printer) reset() {
p.Buffer.Reset()
- p.fmt.argNum = 0
- p.fmt.startPos = 0
- p.fmt.reordered = false
p.panicking = false
p.erroring = false
p.fmt.init(&p.Buffer)
@@ -78,22 +75,22 @@
// Language implements "golang.org/x/text/internal/format".State.
func (p *printer) Language() language.Tag { return p.tag }
-func (p *printer) Width() (wid int, ok bool) { return p.fmt.wid, p.fmt.widPresent }
+func (p *printer) Width() (wid int, ok bool) { return p.fmt.Width, p.fmt.WidthPresent }
-func (p *printer) Precision() (prec int, ok bool) { return p.fmt.prec, p.fmt.precPresent }
+func (p *printer) Precision() (prec int, ok bool) { return p.fmt.Prec, p.fmt.PrecPresent }
func (p *printer) Flag(b int) bool {
switch b {
case '-':
- return p.fmt.minus
+ return p.fmt.Minus
case '+':
- return p.fmt.plus || p.fmt.plusV
+ return p.fmt.Plus || p.fmt.PlusV
case '#':
- return p.fmt.sharp || p.fmt.sharpV
+ return p.fmt.Sharp || p.fmt.SharpV
case ' ':
- return p.fmt.space
+ return p.fmt.Space
case '0':
- return p.fmt.zero
+ return p.fmt.Zero
}
return false
}
@@ -109,28 +106,6 @@
return val
}
-// tooLarge reports whether the magnitude of the integer is
-// too large to be used as a formatting width or precision.
-func tooLarge(x int) bool {
- const max int = 1e6
- return x > max || x < -max
-}
-
-// parsenum converts ASCII to integer. num is 0 (and isnum is false) if no number present.
-func parsenum(s string, start, end int) (num int, isnum bool, newi int) {
- if start >= end {
- return 0, false, end
- }
- for newi = start; newi < end && '0' <= s[newi] && s[newi] <= '9'; newi++ {
- if tooLarge(num) {
- return 0, false, end // Overflow; crazy long number most likely.
- }
- num = num*10 + int(s[newi]-'0')
- isnum = true
- }
- return
-}
-
func (p *printer) unknownType(v reflect.Value) {
if !v.IsValid() {
p.WriteString(nilAngleString)
@@ -174,23 +149,23 @@
// fmt0x64 formats a uint64 in hexadecimal and prefixes it with 0x or
// not, as requested, by temporarily setting the sharp flag.
func (p *printer) fmt0x64(v uint64, leading0x bool) {
- sharp := p.fmt.sharp
- p.fmt.sharp = leading0x
+ sharp := p.fmt.Sharp
+ p.fmt.Sharp = leading0x
p.fmt.fmt_integer(v, 16, unsigned, ldigits)
- p.fmt.sharp = sharp
+ p.fmt.Sharp = sharp
}
// fmtInteger formats a signed or unsigned integer.
func (p *printer) fmtInteger(v uint64, isSigned bool, verb rune) {
switch verb {
case 'v':
- if p.fmt.sharpV && !isSigned {
+ if p.fmt.SharpV && !isSigned {
p.fmt0x64(v, true)
return
}
fallthrough
case 'd':
- if p.fmt.sharp || p.fmt.sharpV {
+ if p.fmt.Sharp || p.fmt.SharpV {
p.fmt.fmt_integer(v, 10, isSigned, ldigits)
} else {
p.fmtDecimalInt(v, isSigned)
@@ -228,19 +203,19 @@
verb = 'g'
fallthrough
case 'g', 'G':
- if p.fmt.sharp || p.fmt.sharpV {
+ if p.fmt.Sharp || p.fmt.SharpV {
p.fmt.fmt_float(v, size, verb, -1)
} else {
p.fmtVariableFloat(v, size)
}
case 'e', 'E':
- if p.fmt.sharp || p.fmt.sharpV {
+ if p.fmt.Sharp || p.fmt.SharpV {
p.fmt.fmt_float(v, size, verb, 6)
} else {
p.fmtScientific(v, size, 6)
}
case 'f', 'F':
- if p.fmt.sharp || p.fmt.sharpV {
+ if p.fmt.Sharp || p.fmt.SharpV {
p.fmt.fmt_float(v, size, verb, 6)
} else {
p.fmtDecimalFloat(v, size, 6)
@@ -252,9 +227,9 @@
func (p *printer) setFlags(f *number.Formatter) {
f.Flags &^= number.ElideSign
- if p.fmt.plus || p.fmt.space {
+ if p.fmt.Plus || p.fmt.Space {
f.Flags |= number.AlwaysSign
- if !p.fmt.plus {
+ if !p.fmt.Plus {
f.Flags |= number.ElideSign
}
} else {
@@ -264,13 +239,13 @@
func (p *printer) updatePadding(f *number.Formatter) {
f.Flags &^= number.PadMask
- if p.fmt.minus {
+ if p.fmt.Minus {
f.Flags |= number.PadAfterSuffix
} else {
f.Flags |= number.PadBeforePrefix
}
f.PadRune = ' '
- f.FormatWidth = uint16(p.fmt.wid)
+ f.FormatWidth = uint16(p.fmt.Width)
}
func (p *printer) initDecimal(minFrac, maxFrac int) {
@@ -281,15 +256,15 @@
f.MaxFractionDigits = int16(maxFrac)
p.setFlags(f)
f.PadRune = 0
- if p.fmt.widPresent {
- if p.fmt.zero {
- wid := p.fmt.wid
+ if p.fmt.WidthPresent {
+ if p.fmt.Zero {
+ wid := p.fmt.Width
// Use significant integers for this.
// TODO: this is not the same as width, but so be it.
if f.MinFractionDigits > 0 {
wid -= 1 + int(f.MinFractionDigits)
}
- if p.fmt.plus || p.fmt.space {
+ if p.fmt.Plus || p.fmt.Space {
wid--
}
if wid > 0 && wid > int(f.MinIntegerDigits) {
@@ -312,9 +287,9 @@
f.MinExponentDigits = 2
p.setFlags(f)
f.PadRune = 0
- if p.fmt.widPresent {
+ if p.fmt.WidthPresent {
f.Flags &^= number.PadMask
- if p.fmt.zero {
+ if p.fmt.Zero {
f.PadRune = f.Digit(0)
f.Flags |= number.PadAfterPrefix
} else {
@@ -329,13 +304,13 @@
var d number.Decimal
f := &p.toDecimal
- if p.fmt.precPresent {
+ if p.fmt.PrecPresent {
p.setFlags(f)
- f.MinIntegerDigits = uint8(p.fmt.prec)
+ f.MinIntegerDigits = uint8(p.fmt.Prec)
f.MaxIntegerDigits = 0
f.MinFractionDigits = 0
f.MaxFractionDigits = 0
- if p.fmt.widPresent {
+ if p.fmt.WidthPresent {
p.updatePadding(f)
}
} else {
@@ -349,8 +324,8 @@
func (p *printer) fmtDecimalFloat(v float64, size, prec int) {
var d number.Decimal
- if p.fmt.precPresent {
- prec = p.fmt.prec
+ if p.fmt.PrecPresent {
+ prec = p.fmt.Prec
}
p.initDecimal(prec, prec)
d.ConvertFloat(p.toDecimal.RoundingContext, v, size)
@@ -361,8 +336,8 @@
func (p *printer) fmtVariableFloat(v float64, size int) {
prec := -1
- if p.fmt.precPresent {
- prec = p.fmt.prec
+ if p.fmt.PrecPresent {
+ prec = p.fmt.Prec
}
var d number.Decimal
p.initScientific(0, prec)
@@ -401,8 +376,8 @@
func (p *printer) fmtScientific(v float64, size, prec int) {
var d number.Decimal
- if p.fmt.precPresent {
- prec = p.fmt.prec
+ if p.fmt.PrecPresent {
+ prec = p.fmt.Prec
}
p.initScientific(prec, prec)
rc := p.toScientific.RoundingContext
@@ -450,11 +425,11 @@
p.WriteString("i)")
return
}
- oldPlus := p.fmt.plus
- p.fmt.plus = true
+ oldPlus := p.fmt.Plus
+ p.fmt.Plus = true
p.fmtFloat(imag(v), size/2, verb)
p.WriteString("i)") // TODO: use symbol?
- p.fmt.plus = oldPlus
+ p.fmt.Plus = oldPlus
default:
p.badVerb(verb)
}
@@ -463,7 +438,7 @@
func (p *printer) fmtString(v string, verb rune) {
switch verb {
case 'v':
- if p.fmt.sharpV {
+ if p.fmt.SharpV {
p.fmt.fmt_q(v)
} else {
p.fmt.fmt_s(v)
@@ -484,7 +459,7 @@
func (p *printer) fmtBytes(v []byte, verb rune, typeString string) {
switch verb {
case 'v', 'd':
- if p.fmt.sharpV {
+ if p.fmt.SharpV {
p.WriteString(typeString)
if v == nil {
p.WriteString(nilParenString)
@@ -533,7 +508,7 @@
switch verb {
case 'v':
- if p.fmt.sharpV {
+ if p.fmt.SharpV {
p.WriteByte('(')
p.WriteString(value.Type().String())
p.WriteString(")(")
@@ -547,14 +522,14 @@
if u == 0 {
p.fmt.padString(nilAngleString)
} else {
- p.fmt0x64(uint64(u), !p.fmt.sharp)
+ p.fmt0x64(uint64(u), !p.fmt.Sharp)
}
}
case 'p':
- p.fmt0x64(uint64(u), !p.fmt.sharp)
+ p.fmt0x64(uint64(u), !p.fmt.Sharp)
case 'b', 'o', 'd', 'x', 'X':
if verb == 'd' {
- p.fmt.sharp = true // Print as standard go. TODO: does this make sense?
+ p.fmt.Sharp = true // Print as standard go. TODO: does this make sense?
}
p.fmtInteger(uint64(u), unsigned, verb)
default:
@@ -578,9 +553,9 @@
panic(err)
}
- oldFlags := p.fmt.fmtFlags
+ oldFlags := p.fmt.Parser
// For this output we want default behavior.
- p.fmt.clearflags()
+ p.fmt.ClearFlags()
p.WriteString(percentBangString)
p.WriteRune(verb)
@@ -590,7 +565,7 @@
p.panicking = false
p.WriteByte(')')
- p.fmt.fmtFlags = oldFlags
+ p.fmt.Parser = oldFlags
}
}
@@ -613,7 +588,7 @@
}
// If we're doing Go syntax and the argument knows how to supply it, take care of it now.
- if p.fmt.sharpV {
+ if p.fmt.SharpV {
if stringer, ok := p.arg.(fmt.GoStringer); ok {
handled = true
defer p.catchPanic(p.arg, verb)
@@ -774,7 +749,7 @@
case reflect.String:
p.fmtString(f.String(), verb)
case reflect.Map:
- if p.fmt.sharpV {
+ if p.fmt.SharpV {
p.WriteString(f.Type().String())
if f.IsNil() {
p.WriteString(nilParenString)
@@ -787,7 +762,7 @@
keys := f.MapKeys()
for i, key := range keys {
if i > 0 {
- if p.fmt.sharpV {
+ if p.fmt.SharpV {
p.WriteString(commaSpaceString)
} else {
p.WriteByte(' ')
@@ -797,25 +772,25 @@
p.WriteByte(':')
p.printValue(f.MapIndex(key), verb, depth+1)
}
- if p.fmt.sharpV {
+ if p.fmt.SharpV {
p.WriteByte('}')
} else {
p.WriteByte(']')
}
case reflect.Struct:
- if p.fmt.sharpV {
+ if p.fmt.SharpV {
p.WriteString(f.Type().String())
}
p.WriteByte('{')
for i := 0; i < f.NumField(); i++ {
if i > 0 {
- if p.fmt.sharpV {
+ if p.fmt.SharpV {
p.WriteString(commaSpaceString)
} else {
p.WriteByte(' ')
}
}
- if p.fmt.plusV || p.fmt.sharpV {
+ if p.fmt.PlusV || p.fmt.SharpV {
if name := f.Type().Field(i).Name; name != "" {
p.WriteString(name)
p.WriteByte(':')
@@ -827,7 +802,7 @@
case reflect.Interface:
value := f.Elem()
if !value.IsValid() {
- if p.fmt.sharpV {
+ if p.fmt.SharpV {
p.WriteString(f.Type().String())
p.WriteString(nilParenString)
} else {
@@ -860,7 +835,7 @@
return
}
}
- if p.fmt.sharpV {
+ if p.fmt.SharpV {
p.WriteString(f.Type().String())
if f.Kind() == reflect.Slice && f.IsNil() {
p.WriteString(nilParenString)
@@ -903,81 +878,6 @@
}
}
-// intFromArg gets the argNumth element of a. On return, isInt reports whether the argument has integer type.
-func (p *fmtFlags) intFromArg() (num int, isInt bool) {
- if p.argNum < len(p.args) {
- arg := p.args[p.argNum]
- num, isInt = arg.(int) // Almost always OK.
- if !isInt {
- // Work harder.
- switch v := reflect.ValueOf(arg); v.Kind() {
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- n := v.Int()
- if int64(int(n)) == n {
- num = int(n)
- isInt = true
- }
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- n := v.Uint()
- if int64(n) >= 0 && uint64(int(n)) == n {
- num = int(n)
- isInt = true
- }
- default:
- // Already 0, false.
- }
- }
- p.argNum++
- if tooLarge(num) {
- num = 0
- isInt = false
- }
- }
- return
-}
-
-// parseArgNumber returns the value of the bracketed number, minus 1
-// (explicit argument numbers are one-indexed but we want zero-indexed).
-// The opening bracket is known to be present at format[0].
-// The returned values are the index, the number of bytes to consume
-// up to the closing paren, if present, and whether the number parsed
-// ok. The bytes to consume will be 1 if no closing paren is present.
-func parseArgNumber(format string) (index int, wid int, ok bool) {
- // There must be at least 3 bytes: [n].
- if len(format) < 3 {
- return 0, 1, false
- }
-
- // Find closing bracket.
- for i := 1; i < len(format); i++ {
- if format[i] == ']' {
- width, ok, newi := parsenum(format, 1, i)
- if !ok || newi != i {
- return 0, i + 1, false
- }
- return width - 1, i + 1, true // arg numbers are one-indexed and skip paren.
- }
- }
- return 0, 1, false
-}
-
-// updateArgNumber returns the next argument to evaluate, which is either the value of the passed-in
-// argNum or the value of the bracketed integer that begins format[i:]. It also returns
-// the new value of i, that is, the index of the next byte of the format to process.
-func (p *fmtFlags) updateArgNumber(format string, i int) (newi int, found bool) {
- if len(format) <= i || format[i] != '[' {
- return i, false
- }
- p.reordered = true
- index, wid, ok := parseArgNumber(format[i:])
- if ok && 0 <= index && index < len(p.args) {
- p.argNum = index
- return i + wid, true
- }
- p.goodArgNum = false
- return i + wid, ok
-}
-
func (p *printer) badArgNum(verb rune) {
p.WriteString(percentBangString)
p.WriteRune(verb)
@@ -990,182 +890,25 @@
p.WriteString(missingString)
}
-func (p *fmtFlags) Scan() bool {
- p.state = text
- format := p.format
- end := len(format)
- if p.endPos >= end {
- return false
- }
- afterIndex := false // previous item in format was an index like [3].
-
- p.startPos = p.endPos
- p.goodArgNum = true
- i := p.startPos
- for i < end && format[i] != '%' {
- i++
- }
- if i > p.startPos {
- p.endPos = i
- return true
- }
- // Process one verb
- i++
-
- p.state = substitution
-
- // Do we have flags?
- p.clearflags()
-
-simpleFormat:
- for ; i < end; i++ {
- c := p.format[i]
- switch c {
- case '#':
- p.sharp = true
- case '0':
- p.zero = !p.minus // Only allow zero padding to the left.
- case '+':
- p.plus = true
- case '-':
- p.minus = true
- p.zero = false // Do not pad with zeros to the right.
- case ' ':
- p.space = true
- default:
- // Fast path for common case of ascii lower case simple verbs
- // without precision or width or argument indices.
- if 'a' <= c && c <= 'z' && p.argNum < len(p.args) {
- if c == 'v' {
- // Go syntax
- p.sharpV = p.sharp
- p.sharp = false
- // Struct-field syntax
- p.plusV = p.plus
- p.plus = false
- }
- p.verb = rune(c)
- p.argNum++
- p.endPos = i + 1
- return true
- }
- // Format is more complex than simple flags and a verb or is malformed.
- break simpleFormat
- }
- }
-
- // Do we have an explicit argument index?
- i, afterIndex = p.updateArgNumber(format, i)
-
- // Do we have width?
- if i < end && format[i] == '*' {
- i++
- p.wid, p.widPresent = p.intFromArg()
-
- if !p.widPresent {
- p.state = badWidth
- }
-
- // We have a negative width, so take its value and ensure
- // that the minus flag is set
- if p.wid < 0 {
- p.wid = -p.wid
- p.minus = true
- p.zero = false // Do not pad with zeros to the right.
- }
- afterIndex = false
- } else {
- p.wid, p.widPresent, i = parsenum(format, i, end)
- if afterIndex && p.widPresent { // "%[3]2d"
- p.goodArgNum = false
- }
- }
-
- // Do we have precision?
- if i+1 < end && format[i] == '.' {
- i++
- if afterIndex { // "%[3].2d"
- p.goodArgNum = false
- }
- i, afterIndex = p.updateArgNumber(format, i)
- if i < end && format[i] == '*' {
- i++
- p.prec, p.precPresent = p.intFromArg()
- // Negative precision arguments don't make sense
- if p.prec < 0 {
- p.prec = 0
- p.precPresent = false
- }
- if !p.precPresent {
- p.state = badPrec
- }
- afterIndex = false
- } else {
- p.prec, p.precPresent, i = parsenum(format, i, end)
- if !p.precPresent {
- p.prec = 0
- p.precPresent = true
- }
- }
- }
-
- if !afterIndex {
- i, afterIndex = p.updateArgNumber(format, i)
- }
-
- if i >= end {
- p.endPos = i
- p.state = noVerb
- return true
- }
-
- verb, w := utf8.DecodeRuneInString(format[i:])
- p.endPos = i + w
- p.verb = verb
-
- switch {
- case verb == '%': // Percent does not absorb operands and ignores f.wid and f.prec.
- p.startPos = p.endPos - 1
- p.state = text
- case !p.goodArgNum:
- p.state = badArgNum
- case p.argNum >= len(p.args): // No argument left over to print for the current verb.
- p.state = missingArg
- case verb == 'v':
- // Go syntax
- p.sharpV = p.sharp
- p.sharp = false
- // Struct-field syntax
- p.plusV = p.plus
- p.plus = false
- fallthrough
- default:
- p.argNum++
- }
- return true
-}
-
-func (p *printer) doPrintf(format string) {
- p.fmt.fmtFlags.init(format)
-
- for p.fmt.Scan() {
- switch p.fmt.state {
- case text:
+func (p *printer) doPrintf(fmt string) {
+ for p.fmt.Parser.SetFormat(fmt); p.fmt.Scan(); {
+ switch p.fmt.Status {
+ case format.StatusText:
p.WriteString(p.fmt.Text())
- case substitution:
- p.printArg(p.Arg(p.fmt.argNum), p.fmt.verb)
- case badWidth:
+ case format.StatusSubstitution:
+ p.printArg(p.Arg(p.fmt.ArgNum), p.fmt.Verb)
+ case format.StatusBadWidthSubstitution:
p.WriteString(badWidthString)
- p.printArg(p.Arg(p.fmt.argNum), p.fmt.verb)
- case badPrec:
+ p.printArg(p.Arg(p.fmt.ArgNum), p.fmt.Verb)
+ case format.StatusBadPrecSubstitution:
p.WriteString(badPrecString)
- p.printArg(p.Arg(p.fmt.argNum), p.fmt.verb)
- case noVerb:
+ p.printArg(p.Arg(p.fmt.ArgNum), p.fmt.Verb)
+ case format.StatusNoVerb:
p.WriteString(noVerbString)
- case badArgNum:
- p.badArgNum(p.fmt.verb)
- case missingArg:
- p.missingArg(p.fmt.verb)
+ case format.StatusBadArgNum:
+ p.badArgNum(p.fmt.Verb)
+ case format.StatusMissingArg:
+ p.missingArg(p.fmt.Verb)
default:
panic("unreachable")
}
@@ -1175,10 +918,10 @@
// argument. Note that this behavior is necessarily different from fmt:
// different variants of messages may opt to drop some or all of the
// arguments.
- if !p.fmt.reordered && p.fmt.argNum < len(p.fmt.args) && p.fmt.argNum != 0 {
- p.fmt.clearflags()
+ if !p.fmt.Reordered && p.fmt.ArgNum < len(p.fmt.Args) && p.fmt.ArgNum != 0 {
+ p.fmt.ClearFlags()
p.WriteString(extraString)
- for i, arg := range p.fmt.args[p.fmt.argNum:] {
+ for i, arg := range p.fmt.Args[p.fmt.ArgNum:] {
if i > 0 {
p.WriteString(commaSpaceString)
}