Search Apps Documentation Source Content File Folder Download Copy

ufmt.gno

10.28 Kb · 411 lines
  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}