Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip(keeper): implement json primitive return #2949

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions examples/gno.land/r/demo/users/users.gno
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var (
func Register(inviter std.Address, name string, profile string) {
// assert CallTx call.
std.AssertOriginCall()

// assert invited or paid.
caller := std.GetCallerAt(2)
if caller != std.GetOrigCaller() {
Expand Down
1 change: 1 addition & 0 deletions gno.land/pkg/gnoland/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ func EndBlocker(
ctx,
valRealm,
fmt.Sprintf("%s(%d)", valChangesFn, app.LastBlockHeight()),
vm.FormatMachine,
)
if err != nil {
app.Logger().Error("unable to call VM during EndBlocker", "err", err)
Expand Down
93 changes: 93 additions & 0 deletions gno.land/pkg/sdk/vm/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,99 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) {
}
}

func JSONPrimitiveValues(m *gno.Machine, tvs []gno.TypedValue) string {
var str strings.Builder

str.WriteRune('[')
for i, tv := range tvs {
if i > 0 {
str.WriteRune(',')
}
str.WriteString(JSONPrimitiveValue(m, tv))
}
str.WriteRune(']')

return str.String()
}

func JSONPrimitiveValue(m *gno.Machine, tv gno.TypedValue) string {
if tv.T == nil {
return "null"
}

switch bt := gno.BaseOf(tv.T).(type) {
case gno.PrimitiveType:
switch bt {
case gno.IntType:
return fmt.Sprintf("%d", tv.GetInt())
case gno.Int8Type:
return fmt.Sprintf("%d", tv.GetInt8())
case gno.Int16Type:
return fmt.Sprintf("%d", tv.GetInt16())
case gno.UntypedRuneType, gno.Int32Type:
return fmt.Sprintf("%d", tv.GetInt32())
case gno.Int64Type:
return fmt.Sprintf("%d", tv.GetInt64())
case gno.UintType:
return fmt.Sprintf("%d", tv.GetUint())
case gno.Uint8Type:
return fmt.Sprintf("%d", tv.GetUint8())
case gno.DataByteType:
return fmt.Sprintf("%d", tv.GetDataByte())
case gno.Uint16Type:
return fmt.Sprintf("%d", tv.GetUint16())
case gno.Uint32Type:
return fmt.Sprintf("%d", tv.GetUint32())
case gno.Uint64Type:
return fmt.Sprintf("%d", tv.GetUint64())
case gno.Float32Type:
return fmt.Sprintf("%f", tv.GetFloat32())
case gno.Float64Type:
return fmt.Sprintf("%f", tv.GetFloat64())
case gno.UntypedBigintType, gno.BigintType:
return tv.V.(gno.BigintValue).V.String()
case gno.UntypedBigdecType, gno.BigdecType:
return tv.V.(gno.BigdecValue).V.String()
case gno.UntypedBoolType, gno.BoolType:
return fmt.Sprintf("%t", tv.GetBool())
case gno.UntypedStringType, gno.StringType:
return strconv.Quote(tv.GetString())
default:
panic("invalid primitive type - should not happen")
}
case *gno.PointerType:
// Check if Pointer we type implement Error
// If implements .Error(), return it.
if tv.IsError() {
res := m.Eval(gno.Call(gno.Sel(&gno.ConstExpr{TypedValue: tv}, "Error")))
return strconv.Quote(res[0].GetString())
}
default:
// Check if pointer wraped value can implement Error
ptv := gno.TypedValue{
T: &gno.PointerType{Elt: tv.T},
V: gno.PointerValue{TV: &tv, Base: tv.V},
}

// If implements .Error(), return it.
if ptv.IsError() {
res := m.Eval(gno.Call(gno.Sel(&gno.ConstExpr{TypedValue: ptv}, "Error")))
return strconv.Quote(res[0].GetString())
}
}

if tv.V == nil {
return "null"
}

var id string
if pv, ok := tv.V.(gno.PointerValue); ok {
id = pv.GetBase(m.Store).GetObjectID().String()
}

return strconv.Quote(fmt.Sprintf(`<%s:%s>`, tv.T.String(), id))
}

func convertFloat(value string, precision int) float64 {
assertNoPlusPrefix(value)
dec, _, err := apd.NewFromString(value)
Expand Down
161 changes: 161 additions & 0 deletions gno.land/pkg/sdk/vm/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package vm

import (
"fmt"
"strconv"
"strings"
"testing"

"github.com/gnolang/gno/gnovm/pkg/gnolang"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestConvertEmptyNumbers(t *testing.T) {
Expand Down Expand Up @@ -37,3 +40,161 @@ func TestConvertEmptyNumbers(t *testing.T) {
})
}
}

func TestConvertJSONValuePrimtive(t *testing.T) {
cases := []struct {
ValueRep string // Go representation
Expected string // string representation
}{
// Boolean
{"nil", "null"},

// Boolean
{"true", "true"},
{"false", "false"},

// int types
{"int(42)", `42`}, // Needs to be quoted for amino
{"int8(42)", `42`},
{"int16(42)", `42`},
{"int32(42)", `42`},
{"int64(42)", `42`},

// uint types
{"uint(42)", `42`},
{"uint8(42)", `42`},
{"uint16(42)", `42`},
{"uint32(42)", `42`},
{"uint64(42)", `42`},

// Float types
{"float32(3.14)", "3.14"},
{"float64(3.14)", "3.14"},

// String type
{`"hello world"`, `"hello world"`},
}

for _, tc := range cases {
t.Run(tc.ValueRep, func(t *testing.T) {
m := gnolang.NewMachine("testdata", nil)
defer m.Release()

nn := gnolang.MustParseFile("testdata.gno",
fmt.Sprintf(`package testdata; var Value = %s`, tc.ValueRep))
m.RunFiles(nn)
m.RunDeclaration(gnolang.ImportD("testdata", "testdata"))

tps := m.Eval(gnolang.Sel(gnolang.Nx("testdata"), "Value"))
require.Len(t, tps, 1)

tv := tps[0]

rep := JSONPrimitiveValue(m, tv)
require.Equal(t, tc.Expected, rep)
})
}
}

func TestConvertJSONValueStruct(t *testing.T) {
const StructsFile = `
package testdata

// E struct, impement error
type E struct { S string }

func (e *E) Error() string { return e.S }
`

t.Run("null pointer", func(t *testing.T) {
m := gnolang.NewMachine("testdata", nil)
defer m.Release()

const expected = "Hello World"
nn := gnolang.MustParseFile("struct.gno", StructsFile)
m.RunFiles(nn)
nn = gnolang.MustParseFile("testdata.gno",
fmt.Sprintf(`package testdata; var Value *E = nil`, expected))
m.RunFiles(nn)
m.RunDeclaration(gnolang.ImportD("testdata", "testdata"))

tps := m.Eval(gnolang.Sel(gnolang.Nx("testdata"), "Value"))
require.Len(t, tps, 1)

tv := tps[0]
rep := JSONPrimitiveValue(m, tv)
require.Equal(t, strconv.Quote(expected), rep)
})

t.Run("without pointer", func(t *testing.T) {
m := gnolang.NewMachine("testdata", nil)
defer m.Release()

const expected = "Hello World"
nn := gnolang.MustParseFile("struct.gno", StructsFile)
m.RunFiles(nn)
nn = gnolang.MustParseFile("testdata.gno",
fmt.Sprintf(`package testdata; var Value = E{%q}`, expected))
m.RunFiles(nn)
m.RunDeclaration(gnolang.ImportD("testdata", "testdata"))

tps := m.Eval(gnolang.Sel(gnolang.Nx("testdata"), "Value"))
require.Len(t, tps, 1)

tv := tps[0]
rep := JSONPrimitiveValue(m, tv)
require.Equal(t, strconv.Quote(expected), rep)
})

t.Run("with pointer", func(t *testing.T) {
m := gnolang.NewMachine("testdata", nil)
defer m.Release()

const expected = "Hello World"
nn := gnolang.MustParseFile("struct.gno", StructsFile)
m.RunFiles(nn)
nn = gnolang.MustParseFile("testdata.gno",
fmt.Sprintf(`package testdata; var Value = &E{%q}`, expected))
m.RunFiles(nn)
m.RunDeclaration(gnolang.ImportD("testdata", "testdata"))

tps := m.Eval(gnolang.Sel(gnolang.Nx("testdata"), "Value"))
require.Len(t, tps, 1)

tv := tps[0]
rep := JSONPrimitiveValue(m, tv)
require.Equal(t, strconv.Quote(expected), rep)
})
}

func TestConvertJSONValuesList(t *testing.T) {
cases := []struct {
ValueRep []string // Go representation
Expected string // string representation
}{
// Boolean
{[]string{}, "[]"},
{[]string{"42"}, "[42]"},
{[]string{"42", `"hello world"`}, `[42,"hello world"]`},
{[]string{"42", `"hello world"`, "[]int{42}"}, `[42,"hello world"]`},
}

for _, tc := range cases {
t.Run(strings.Join(tc.ValueRep, "-"), func(t *testing.T) {
m := gnolang.NewMachine("testdata", nil)
defer m.Release()

nn := gnolang.MustParseFile("testdata.gno",
fmt.Sprintf(`package testdata; var Value = []interface{}{%s}`, strings.Join(tc.ValueRep, ",")))
m.RunFiles(nn)
m.RunDeclaration(gnolang.ImportD("testdata", "testdata"))

tps := m.Eval(gnolang.Sel(gnolang.Nx("testdata"), "Value"))
require.Len(t, tps, 1)
require.Equal(t, gnolang.SliceKind.String(), tps[0].T.Kind().String())
tpvs := tps[0].V.(*gnolang.SliceValue).Base.(*gnolang.ArrayValue).List
rep := JSONPrimitiveValues(m, tpvs)
require.Equal(t, tc.Expected, rep)
})
}
}
Loading
Loading