printf.go raw
1 // Package printf implements a parser for fmt.Printf-style format
2 // strings.
3 //
4 // It parses verbs according to the following syntax:
5 //
6 // Numeric -> '0'-'9'
7 // Letter -> 'a'-'z' | 'A'-'Z'
8 // Index -> '[' Numeric+ ']'
9 // Star -> '*'
10 // Star -> Index '*'
11 //
12 // Precision -> Numeric+ | Star
13 // Width -> Numeric+ | Star
14 //
15 // WidthAndPrecision -> Width '.' Precision
16 // WidthAndPrecision -> Width '.'
17 // WidthAndPrecision -> Width
18 // WidthAndPrecision -> '.' Precision
19 // WidthAndPrecision -> '.'
20 //
21 // Flag -> '+' | '-' | '#' | ' ' | '0'
22 // Verb -> Letter | '%'
23 //
24 // Input -> '%' [ Flag+ ] [ WidthAndPrecision ] [ Index ] Verb
25 package printf
26
27 import (
28 "errors"
29 "regexp"
30 "strconv"
31 "strings"
32 )
33
34 // ErrInvalid is returned for invalid format strings or verbs.
35 var ErrInvalid = errors.New("invalid format string")
36
37 type Verb struct {
38 Letter rune
39 Flags string
40
41 Width Argument
42 Precision Argument
43 // Which value in the argument list the verb uses.
44 // -1 denotes the next argument,
45 // values > 0 denote explicit arguments.
46 // The value 0 denotes that no argument is consumed. This is the case for %%.
47 Value int
48
49 Raw string
50 }
51
52 // Argument is an implicit or explicit width or precision.
53 type Argument interface {
54 isArgument()
55 }
56
57 // The Default value, when no width or precision is provided.
58 type Default struct{}
59
60 // Zero is the implicit zero value.
61 // This value may only appear for precisions in format strings like %6.f
62 type Zero struct{}
63
64 // Star is a * value, which may either refer to the next argument (Index == -1) or an explicit argument.
65 type Star struct{ Index int }
66
67 // A Literal value, such as 6 in %6d.
68 type Literal int
69
70 func (Default) isArgument() {}
71 func (Zero) isArgument() {}
72 func (Star) isArgument() {}
73 func (Literal) isArgument() {}
74
75 // Parse parses f and returns a list of actions.
76 // An action may either be a literal string, or a Verb.
77 func Parse(f string) ([]interface{}, error) {
78 var out []interface{}
79 for len(f) > 0 {
80 if f[0] == '%' {
81 v, n, err := ParseVerb(f)
82 if err != nil {
83 return nil, err
84 }
85 f = f[n:]
86 out = append(out, v)
87 } else {
88 n := strings.IndexByte(f, '%')
89 if n > -1 {
90 out = append(out, f[:n])
91 f = f[n:]
92 } else {
93 out = append(out, f)
94 f = ""
95 }
96 }
97 }
98
99 return out, nil
100 }
101
102 func atoi(s string) int {
103 n, _ := strconv.Atoi(s)
104 return n
105 }
106
107 // ParseVerb parses the verb at the beginning of f.
108 // It returns the verb, how much of the input was consumed, and an error, if any.
109 func ParseVerb(f string) (Verb, int, error) {
110 if len(f) < 2 {
111 return Verb{}, 0, ErrInvalid
112 }
113 const (
114 flags = 1
115
116 width = 2
117 widthStar = 3
118 widthIndex = 5
119
120 dot = 6
121 prec = 7
122 precStar = 8
123 precIndex = 10
124
125 verbIndex = 11
126 verb = 12
127 )
128
129 m := re.FindStringSubmatch(f)
130 if m == nil {
131 return Verb{}, 0, ErrInvalid
132 }
133
134 v := Verb{
135 Letter: []rune(m[verb])[0],
136 Flags: m[flags],
137 Raw: m[0],
138 }
139
140 if m[width] != "" {
141 // Literal width
142 v.Width = Literal(atoi(m[width]))
143 } else if m[widthStar] != "" {
144 // Star width
145 if m[widthIndex] != "" {
146 v.Width = Star{atoi(m[widthIndex])}
147 } else {
148 v.Width = Star{-1}
149 }
150 } else {
151 // Default width
152 v.Width = Default{}
153 }
154
155 if m[dot] == "" {
156 // default precision
157 v.Precision = Default{}
158 } else {
159 if m[prec] != "" {
160 // Literal precision
161 v.Precision = Literal(atoi(m[prec]))
162 } else if m[precStar] != "" {
163 // Star precision
164 if m[precIndex] != "" {
165 v.Precision = Star{atoi(m[precIndex])}
166 } else {
167 v.Precision = Star{-1}
168 }
169 } else {
170 // Zero precision
171 v.Precision = Zero{}
172 }
173 }
174
175 if m[verb] == "%" {
176 v.Value = 0
177 } else if m[verbIndex] != "" {
178 v.Value = atoi(m[verbIndex])
179 } else {
180 v.Value = -1
181 }
182
183 return v, len(m[0]), nil
184 }
185
186 const (
187 flags = `([+#0 -]*)`
188 verb = `([a-zA-Z%])`
189 index = `(?:\[([0-9]+)\])`
190 star = `((` + index + `)?\*)`
191 width1 = `([0-9]+)`
192 width2 = star
193 width = `(?:` + width1 + `|` + width2 + `)`
194 precision = width
195 widthAndPrecision = `(?:(?:` + width + `)?(?:(\.)(?:` + precision + `)?)?)`
196 )
197
198 var re = regexp.MustCompile(`^%` + flags + widthAndPrecision + `?` + index + `?` + verb)
199