Skip to content

Commit

Permalink
feat(examples): improve p/demo/ufmt (#2553)
Browse files Browse the repository at this point in the history
Co-authored-by: grepsuzette <[email protected]>
  • Loading branch information
grepsuzette and grepsuzette authored Jul 22, 2024
1 parent fec2d18 commit fa98780
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 13 deletions.
129 changes: 119 additions & 10 deletions examples/gno.land/p/demo/ufmt/ufmt.gno
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package ufmt

import (
"errors"
"strconv"
"strings"
)
Expand All @@ -17,16 +18,20 @@ func Println(args ...interface{}) {
switch v := arg.(type) {
case string:
strs = append(strs, v)
case (interface{ String() string }):
strs = append(strs, v.String())
case error:
strs = append(strs, v.Error())
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
strs = append(strs, Sprintf("%d", v))
case bool:
if v {
strs = append(strs, "true")

continue
} else {
strs = append(strs, "false")
}

strs = append(strs, "false")
case nil:
strs = append(strs, "<nil>")
default:
strs = append(strs, "(unhandled)")
}
Expand All @@ -46,7 +51,9 @@ func Println(args ...interface{}) {
//
// %s: places a string value directly.
// If the value implements the interface interface{ String() string },
// the String() method is called to retrieve the value.
// the String() method is called to retrieve the value. Same about Error()
// string.
// %c: formats the character represented by Unicode code point
// %d: formats an integer value using package "strconv".
// Currently supports only uint, uint64, int, int64.
// %t: formats a boolean value to "true" or "false".
Expand Down Expand Up @@ -88,10 +95,32 @@ func Sprintf(format string, args ...interface{}) string {
switch v := arg.(type) {
case interface{ String() string }:
buf += v.String()
case error:
buf += v.Error()
case string:
buf += v
default:
buf += "(unhandled)"
buf += fallback(verb, v)
}
case "c":
switch v := arg.(type) {
// rune is int32. Exclude overflowing numeric types and dups (byte, int32):
case rune:
buf += string(v)
case int:
buf += string(v)
case int8:
buf += string(v)
case int16:
buf += string(v)
case uint:
buf += string(v)
case uint8:
buf += string(v)
case uint16:
buf += string(v)
default:
buf += fallback(verb, v)
}
case "d":
switch v := arg.(type) {
Expand All @@ -116,7 +145,7 @@ func Sprintf(format string, args ...interface{}) string {
case uint64:
buf += strconv.FormatUint(v, 10)
default:
buf += "(unhandled)"
buf += fallback(verb, v)
}
case "t":
switch v := arg.(type) {
Expand All @@ -127,11 +156,11 @@ func Sprintf(format string, args ...interface{}) string {
buf += "false"
}
default:
buf += "(unhandled)"
buf += fallback(verb, v)
}
// % handled before, as it does not consume an argument
default:
buf += "(unhandled)"
buf += "(unhandled verb: %" + verb + ")"
}

i += 2
Expand All @@ -142,6 +171,85 @@ func Sprintf(format string, args ...interface{}) string {
return buf
}

// This function is used to mimic Go's fmt.Sprintf
// specific behaviour of showing verb/type mismatches,
// where for example:
//
// fmt.Sprintf("%d", "foo") gives "%!d(string=foo)"
//
// Here:
//
// fallback("s", 8) -> "%!s(int=8)"
// fallback("d", nil) -> "%!d(<nil>)", and so on.
func fallback(verb string, arg interface{}) string {
var s string
switch v := arg.(type) {
case string:
s = "string=" + v
case (interface{ String() string }):
s = "string=" + v.String()
case error:
// note: also "string=" in Go fmt
s = "string=" + v.Error()
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
// note: rune, byte would be dups, being aliases
if typename, e := typeToString(v); e != nil {
panic("should not happen")
} else {
s = typename + "=" + Sprintf("%d", v)
}
case bool:
if v {
s = "bool=true"
} else {
s = "bool=false"
}
case nil:
s = "<nil>"
default:
s = "(unhandled)"
}
return "%!" + verb + "(" + s + ")"
}

// Get the name of the type of `v` as a string.
// The recognized type of v is currently limited to native non-composite types.
// An error is returned otherwise.
func typeToString(v interface{}) (string, error) {
switch v.(type) {
case string:
return "string", nil
case int:
return "int", nil
case int8:
return "int8", nil
case int16:
return "int16", nil
case int32:
return "int32", nil
case int64:
return "int64", nil
case uint:
return "uint", nil
case uint8:
return "uint8", nil
case uint16:
return "uint16", nil
case uint32:
return "uint32", nil
case uint64:
return "uint64", nil
case float32:
return "float32", nil
case float64:
return "float64", nil
case bool:
return "bool", nil
default:
return "", errors.New("(unsupported type)")
}
}

// errMsg implements the error interface.
type errMsg struct {
msg string
Expand All @@ -165,7 +273,8 @@ func (e *errMsg) Error() string {
//
// %s: places a string value directly.
// If the value implements the interface interface{ String() string },
// the String() method is called to retrieve the value.
// the String() method is called to retrieve the value. Same for error.
// %c: formats the character represented by Unicode code point
// %d: formats an integer value using package "strconv".
// Currently supports only uint, uint64, int, int64.
// %t: formats a boolean value to "true" or "false".
Expand Down
38 changes: 35 additions & 3 deletions examples/gno.land/p/demo/ufmt/ufmt_test.gno
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ufmt

import (
"errors"
"fmt"
"testing"
)
Expand All @@ -12,13 +13,15 @@ func (stringer) String() string {
}

func TestSprintf(t *testing.T) {
tru := true
cases := []struct {
format string
values []interface{}
expectedOutput string
}{
{"hello %s!", []interface{}{"planet"}, "hello planet!"},
{"hi %%%s!", []interface{}{"worl%d"}, "hi %worl%d!"},
{"%s %c %d %t", []interface{}{"foo", 'α', 421, true}, "foo α 421 true"},
{"string [%s]", []interface{}{"foo"}, "string [foo]"},
{"int [%d]", []interface{}{int(42)}, "int [42]"},
{"int8 [%d]", []interface{}{int8(8)}, "int8 [8]"},
Expand All @@ -32,15 +35,36 @@ func TestSprintf(t *testing.T) {
{"uint64 [%d]", []interface{}{uint64(64)}, "uint64 [64]"},
{"bool [%t]", []interface{}{true}, "bool [true]"},
{"bool [%t]", []interface{}{false}, "bool [false]"},
{"invalid bool [%t]", []interface{}{"invalid"}, "invalid bool [(unhandled)]"},
{"invalid integer [%d]", []interface{}{"invalid"}, "invalid integer [(unhandled)]"},
{"invalid string [%s]", []interface{}{1}, "invalid string [(unhandled)]"},
{"no args", nil, "no args"},
{"finish with %", nil, "finish with %"},
{"stringer [%s]", []interface{}{stringer{}}, "stringer [I'm a stringer]"},
{"â", nil, "â"},
{"Hello, World! 😊", nil, "Hello, World! 😊"},
{"unicode formatting: %s", []interface{}{"😊"}, "unicode formatting: 😊"},
// mismatch printing
{"%s", []interface{}{nil}, "%!s(<nil>)"},
{"%s", []interface{}{421}, "%!s(int=421)"},
{"%s", []interface{}{"z"}, "z"},
{"%s", []interface{}{tru}, "%!s(bool=true)"},
{"%s", []interface{}{'z'}, "%!s(int32=122)"},

{"%c", []interface{}{nil}, "%!c(<nil>)"},
{"%c", []interface{}{421}, "ƥ"},
{"%c", []interface{}{"z"}, "%!c(string=z)"},
{"%c", []interface{}{tru}, "%!c(bool=true)"},
{"%c", []interface{}{'z'}, "z"},

{"%d", []interface{}{nil}, "%!d(<nil>)"},
{"%d", []interface{}{421}, "421"},
{"%d", []interface{}{"z"}, "%!d(string=z)"},
{"%d", []interface{}{tru}, "%!d(bool=true)"},
{"%d", []interface{}{'z'}, "122"},

{"%t", []interface{}{nil}, "%!t(<nil>)"},
{"%t", []interface{}{421}, "%!t(int=421)"},
{"%t", []interface{}{"z"}, "%!t(string=z)"},
{"%t", []interface{}{tru}, "true"},
{"%t", []interface{}{'z'}, "%!t(int32=122)"},
}

for _, tc := range cases {
Expand Down Expand Up @@ -103,6 +127,14 @@ func TestErrorf(t *testing.T) {
}
}

func TestPrintErrors(t *testing.T) {
got := Sprintf("error: %s", errors.New("can I be printed?"))
expectedOutput := "error: can I be printed?"
if got != expectedOutput {
t.Errorf("got %q, want %q.", got, expectedOutput)
}
}

// NOTE: Currently, there is no way to get the output of Println without using os.Stdout,
// so we can only test that it doesn't panic and print arguments well.
func TestPrintln(t *testing.T) {
Expand Down

0 comments on commit fa98780

Please sign in to comment.