Search Apps Documentation Source Content File Folder Download Copy

ufmt.gno

8.21 Kb · 337 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 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}