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