1// Package ufmt provides utility functions for formatting strings, similarly
2// to the Go package "fmt", of which only a subset is currently supported
3// (hence the name µfmt - micro fmt).
4package ufmt
5
6import (
7 "errors"
8 "strconv"
9 "strings"
10)
11
12// Println formats using the default formats for its operands and writes to standard output.
13// Println writes the given arguments to standard output with spaces between arguments
14// and a newline at the end.
15func Println(args ...interface{}) {
16 var strs []string
17 for _, arg := range args {
18 switch v := arg.(type) {
19 case string:
20 strs = append(strs, v)
21 case (interface{ String() string }):
22 strs = append(strs, v.String())
23 case error:
24 strs = append(strs, v.Error())
25 case float64:
26 strs = append(strs, Sprintf("%f", v))
27 case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
28 strs = append(strs, Sprintf("%d", v))
29 case bool:
30 if v {
31 strs = append(strs, "true")
32 } else {
33 strs = append(strs, "false")
34 }
35 case nil:
36 strs = append(strs, "<nil>")
37 default:
38 strs = append(strs, "(unhandled)")
39 }
40 }
41
42 // TODO: remove println after gno supports os.Stdout
43 println(strings.Join(strs, " "))
44}
45
46// Sprintf offers similar functionality to Go's fmt.Sprintf, or the sprintf
47// equivalent available in many languages, including C/C++.
48// The number of args passed must exactly match the arguments consumed by the format.
49// A limited number of formatting verbs and features are currently supported,
50// hence the name ufmt (µfmt, micro-fmt).
51//
52// The currently formatted verbs are the following:
53//
54// %s: places a string value directly.
55// If the value implements the interface interface{ String() string },
56// the String() method is called to retrieve the value. Same about Error()
57// string.
58// %c: formats the character represented by Unicode code point
59// %d: formats an integer value using package "strconv".
60// Currently supports only uint, uint64, int, int64.
61// %f: formats a float value, with a default precision of 6.
62// %e: formats a float with scientific notation; 1.23456e+78
63// %E: formats a float with scientific notation; 1.23456E+78
64// %F: The same as %f
65// %g: formats a float value with %e for large exponents, and %f with full precision for smaller numbers
66// %G: formats a float value with %G for large exponents, and %F with full precision for smaller numbers
67// %t: formats a boolean value to "true" or "false".
68// %x: formats an integer value as a hexadecimal string.
69// Currently supports only uint8, []uint8, [32]uint8.
70// %c: formats a rune value as a string.
71// Currently supports only rune, int.
72// %q: formats a string value as a quoted string.
73// %T: formats the type of the value.
74// %v: formats the value with a default representation appropriate for the value's type
75// %%: outputs a literal %. Does not consume an argument.
76func Sprintf(format string, args ...interface{}) string {
77 // we use runes to handle multi-byte characters
78 sTor := []rune(format)
79 end := len(sTor)
80 argNum := 0
81 argLen := len(args)
82 buf := ""
83
84 for i := 0; i < end; {
85 isLast := i == end-1
86 c := string(sTor[i])
87
88 if isLast || c != "%" {
89 // we don't check for invalid format like a one ending with "%"
90 buf += string(c)
91 i++
92 continue
93 }
94
95 verb := string(sTor[i+1])
96 if verb == "%" {
97 buf += "%"
98 i += 2
99 continue
100 }
101
102 if argNum > argLen {
103 panic("invalid number of arguments to ufmt.Sprintf")
104 }
105 arg := args[argNum]
106 argNum++
107
108 switch verb {
109 case "v":
110 switch v := arg.(type) {
111 case nil:
112 buf += "<nil>"
113 case bool:
114 if v {
115 buf += "true"
116 } else {
117 buf += "false"
118 }
119 case int:
120 buf += strconv.Itoa(v)
121 case int8:
122 buf += strconv.Itoa(int(v))
123 case int16:
124 buf += strconv.Itoa(int(v))
125 case int32:
126 buf += strconv.Itoa(int(v))
127 case int64:
128 buf += strconv.Itoa(int(v))
129 case uint:
130 buf += strconv.FormatUint(uint64(v), 10)
131 case uint8:
132 buf += strconv.FormatUint(uint64(v), 10)
133 case uint16:
134 buf += strconv.FormatUint(uint64(v), 10)
135 case uint32:
136 buf += strconv.FormatUint(uint64(v), 10)
137 case uint64:
138 buf += strconv.FormatUint(v, 10)
139 case float64:
140 buf += strconv.FormatFloat(v, 'g', -1, 64)
141 case string:
142 buf += v
143 case []byte:
144 buf += string(v)
145 case []rune:
146 buf += string(v)
147 case interface{ String() string }:
148 buf += v.String()
149 case error:
150 buf += v.Error()
151 default:
152 buf += fallback(verb, v)
153 }
154 case "s":
155 switch v := arg.(type) {
156 case interface{ String() string }:
157 buf += v.String()
158 case error:
159 buf += v.Error()
160 case string:
161 buf += v
162 default:
163 buf += fallback(verb, v)
164 }
165 case "c":
166 switch v := arg.(type) {
167 // rune is int32. Exclude overflowing numeric types and dups (byte, int32):
168 case rune:
169 buf += string(v)
170 case int:
171 buf += string(v)
172 case int8:
173 buf += string(v)
174 case int16:
175 buf += string(v)
176 case uint:
177 buf += string(v)
178 case uint8:
179 buf += string(v)
180 case uint16:
181 buf += string(v)
182 default:
183 buf += fallback(verb, v)
184 }
185 case "d":
186 switch v := arg.(type) {
187 case int:
188 buf += strconv.Itoa(v)
189 case int8:
190 buf += strconv.Itoa(int(v))
191 case int16:
192 buf += strconv.Itoa(int(v))
193 case int32:
194 buf += strconv.Itoa(int(v))
195 case int64:
196 buf += strconv.Itoa(int(v))
197 case uint:
198 buf += strconv.FormatUint(uint64(v), 10)
199 case uint8:
200 buf += strconv.FormatUint(uint64(v), 10)
201 case uint16:
202 buf += strconv.FormatUint(uint64(v), 10)
203 case uint32:
204 buf += strconv.FormatUint(uint64(v), 10)
205 case uint64:
206 buf += strconv.FormatUint(v, 10)
207 default:
208 buf += fallback(verb, v)
209 }
210 case "e", "E", "f", "F", "g", "G":
211 switch v := arg.(type) {
212 case float64:
213 switch verb {
214 case "e":
215 buf += strconv.FormatFloat(v, byte('e'), -1, 64)
216 case "E":
217 buf += strconv.FormatFloat(v, byte('E'), -1, 64)
218 case "f", "F":
219 buf += strconv.FormatFloat(v, byte('f'), 6, 64)
220 case "g":
221 buf += strconv.FormatFloat(v, byte('g'), -1, 64)
222 case "G":
223 buf += strconv.FormatFloat(v, byte('G'), -1, 64)
224 }
225 default:
226 buf += fallback(verb, v)
227 }
228 case "t":
229 switch v := arg.(type) {
230 case bool:
231 if v {
232 buf += "true"
233 } else {
234 buf += "false"
235 }
236 default:
237 buf += fallback(verb, v)
238 }
239 case "x":
240 switch v := arg.(type) {
241 case uint8:
242 buf += strconv.FormatUint(uint64(v), 16)
243 default:
244 buf += "(unhandled)"
245 }
246 case "q":
247 switch v := arg.(type) {
248 case string:
249 buf += strconv.Quote(v)
250 default:
251 buf += "(unhandled)"
252 }
253 case "T":
254 switch arg.(type) {
255 case bool:
256 buf += "bool"
257 case int:
258 buf += "int"
259 case int8:
260 buf += "int8"
261 case int16:
262 buf += "int16"
263 case int32:
264 buf += "int32"
265 case int64:
266 buf += "int64"
267 case uint:
268 buf += "uint"
269 case uint8:
270 buf += "uint8"
271 case uint16:
272 buf += "uint16"
273 case uint32:
274 buf += "uint32"
275 case uint64:
276 buf += "uint64"
277 case string:
278 buf += "string"
279 case []byte:
280 buf += "[]byte"
281 case []rune:
282 buf += "[]rune"
283 default:
284 buf += "unknown"
285 }
286 // % handled before, as it does not consume an argument
287 default:
288 buf += "(unhandled verb: %" + verb + ")"
289 }
290
291 i += 2
292 }
293 if argNum < argLen {
294 panic("too many arguments to ufmt.Sprintf")
295 }
296 return buf
297}
298
299// This function is used to mimic Go's fmt.Sprintf
300// specific behaviour of showing verb/type mismatches,
301// where for example:
302//
303// fmt.Sprintf("%d", "foo") gives "%!d(string=foo)"
304//
305// Here:
306//
307// fallback("s", 8) -> "%!s(int=8)"
308// fallback("d", nil) -> "%!d(<nil>)", and so on.
309func fallback(verb string, arg interface{}) string {
310 var s string
311 switch v := arg.(type) {
312 case string:
313 s = "string=" + v
314 case (interface{ String() string }):
315 s = "string=" + v.String()
316 case error:
317 // note: also "string=" in Go fmt
318 s = "string=" + v.Error()
319 case float64:
320 s = "float64=" + Sprintf("%f", v)
321 case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
322 // note: rune, byte would be dups, being aliases
323 if typename, e := typeToString(v); e != nil {
324 panic("should not happen")
325 } else {
326 s = typename + "=" + Sprintf("%d", v)
327 }
328 case bool:
329 if v {
330 s = "bool=true"
331 } else {
332 s = "bool=false"
333 }
334 case nil:
335 s = "<nil>"
336 default:
337 s = "(unhandled)"
338 }
339 return "%!" + verb + "(" + s + ")"
340}
341
342// Get the name of the type of `v` as a string.
343// The recognized type of v is currently limited to native non-composite types.
344// An error is returned otherwise.
345func typeToString(v interface{}) (string, error) {
346 switch v.(type) {
347 case string:
348 return "string", nil
349 case int:
350 return "int", nil
351 case int8:
352 return "int8", nil
353 case int16:
354 return "int16", nil
355 case int32:
356 return "int32", nil
357 case int64:
358 return "int64", nil
359 case uint:
360 return "uint", nil
361 case uint8:
362 return "uint8", nil
363 case uint16:
364 return "uint16", nil
365 case uint32:
366 return "uint32", nil
367 case uint64:
368 return "uint64", nil
369 case float32:
370 return "float32", nil
371 case float64:
372 return "float64", nil
373 case bool:
374 return "bool", nil
375 default:
376 return "", errors.New("(unsupported type)")
377 }
378}
379
380// errMsg implements the error interface.
381type errMsg struct {
382 msg string
383}
384
385// Error defines the requirements of the error interface.
386// It functions similarly to Go's errors.New()
387func (e *errMsg) Error() string {
388 return e.msg
389}
390
391// Errorf is a function that mirrors the functionality of fmt.Errorf.
392//
393// It takes a format string and arguments to create a formatted string,
394// then sets this string as the 'msg' field of an errMsg struct and returns a pointer to this struct.
395//
396// This function operates in a similar manner to Go's fmt.Errorf,
397// providing a way to create formatted error messages.
398//
399// The currently formatted verbs are the following:
400//
401// %s: places a string value directly.
402// If the value implements the interface interface{ String() string },
403// the String() method is called to retrieve the value. Same for error.
404// %c: formats the character represented by Unicode code point
405// %d: formats an integer value using package "strconv".
406// Currently supports only uint, uint64, int, int64.
407// %t: formats a boolean value to "true" or "false".
408// %%: outputs a literal %. Does not consume an argument.
409func Errorf(format string, args ...interface{}) error {
410 return &errMsg{Sprintf(format, args...)}
411}
ufmt.gno
10.28 Kb · 411 lines