diff --git a/gno.land/pkg/integration/testdata/gnokey_gasfee.txtar b/gno.land/pkg/integration/testdata/gnokey_gasfee.txtar index 8cd89619c3a..130c643fe17 100644 --- a/gno.land/pkg/integration/testdata/gnokey_gasfee.txtar +++ b/gno.land/pkg/integration/testdata/gnokey_gasfee.txtar @@ -30,8 +30,8 @@ stdout '"coins": "9999999999801ugnot"' # Tx Call -simulate only, estimate gas used and gas fee gnokey maketx call -pkgpath gno.land/r/hello -func Hello -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate only test1 -stdout 'GAS USED: 109082' -stdout 'INFO: estimated gas usage: 109082, gas fee: 115ugnot, current gas price: 1000gas/1ugnot' +stdout 'GAS USED: 110582' +stdout 'INFO: estimated gas usage: 110582, gas fee: 116ugnot, current gas price: 1000gas/1ugnot' ## No fee was charged, and the sequence number did not change. gnokey query auth/accounts/$test1_user_addr @@ -39,13 +39,13 @@ stdout '"sequence": "1"' stdout '"coins": "9999999999801ugnot"' # Using the simulated gas and estimated gas fee should ensure the transaction executes successfully. -gnokey maketx call -pkgpath gno.land/r/hello -func Hello -gas-wanted 109114 -gas-fee 115ugnot -broadcast -chainid tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/hello -func Hello -gas-wanted 110614 -gas-fee 116ugnot -broadcast -chainid tendermint_test test1 stdout 'OK' ## fee is charged and sequence number increased gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "2"' -stdout '"coins": "9999999999686ugnot"' +stdout '"coins": "9999999999685ugnot"' -- hello/gno.mod -- module gno.land/r/hello diff --git a/gno.land/pkg/integration/testdata/simulate_gas.txtar b/gno.land/pkg/integration/testdata/simulate_gas.txtar index 2a0e47a2235..50580ad28b1 100644 --- a/gno.land/pkg/integration/testdata/simulate_gas.txtar +++ b/gno.land/pkg/integration/testdata/simulate_gas.txtar @@ -6,11 +6,11 @@ gnoland start # simulate only gnokey maketx call -pkgpath gno.land/r/simulate -func Hello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate only test1 -stdout 'GAS USED: 105364' +stdout 'GAS USED: 106864' # simulate skip gnokey maketx call -pkgpath gno.land/r/simulate -func Hello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate skip test1 -stdout 'GAS USED: 105364' # same as simulate only +stdout 'GAS USED: 106864' # same as simulate only -- package/package.gno -- diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index 7d4ee0bbce6..dfd9b1fa975 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -505,7 +505,7 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) { defer doRecover(m, &err) rtvs := m.Eval(xn) for i, rtv := range rtvs { - res = res + rtv.String() + res = res + rtv.WriteString(gno.NewStringBuilderWithGasMeter(m.GasMeter)).String() if i < len(rtvs)-1 { res += "\n" } @@ -820,7 +820,7 @@ func (vm *VMKeeper) QueryEval(ctx sdk.Context, pkgPath string, expr string) (res } res = "" for i, rtv := range rtvs { - res += rtv.String() + res += rtv.WriteString(gno.NewStringBuilderWithGasMeter(ctx.GasMeter())).String() if i < len(rtvs)-1 { res += "\n" } diff --git a/gnovm/cmd/benchops/storage_test.go b/gnovm/cmd/benchops/storage_test.go index ecfed989ec3..32b22eb1d13 100644 --- a/gnovm/cmd/benchops/storage_test.go +++ b/gnovm/cmd/benchops/storage_test.go @@ -30,7 +30,7 @@ func TestBenchStoreSet(t *testing.T) { for range rounds { cx := gno.Call("GetPost", gno.X(0), gno.X(0)) res := callFunc(gstore, pv, cx) - parts := strings.Split(res[0].V.String(), ",") + parts := strings.Split(res[0].V.WriteString(gno.NewStringBuilderWithGasMeter(nil)).String(), ",") p := strings.Trim(parts[1], `\"`) expected := strings.Repeat("a", 1024) assert.Equal(p, expected, "it should be 1 KB of character a") diff --git a/gnovm/pkg/gnolang/debugger.go b/gnovm/pkg/gnolang/debugger.go index cf7cfb63956..90d2d0b6317 100644 --- a/gnovm/pkg/gnolang/debugger.go +++ b/gnovm/pkg/gnolang/debugger.go @@ -635,7 +635,7 @@ func debugPrint(m *Machine, arg string) (err error) { if err != nil { return err } - fmt.Fprintln(m.Debugger.out, tv) + fmt.Fprintln(m.Debugger.out, tv.String()) return nil } diff --git a/gnovm/pkg/gnolang/frame.go b/gnovm/pkg/gnolang/frame.go index eb085bd4b8c..dd862fa78e2 100644 --- a/gnovm/pkg/gnolang/frame.go +++ b/gnovm/pkg/gnolang/frame.go @@ -256,11 +256,11 @@ type Exception struct { } func (e *Exception) StringWithStacktrace(m *Machine) string { - return "panic: " + e.Value.Sprint(m) + "\n" + e.Stacktrace.String() + return "panic: " + e.Value.Sprint(NewStringBuilderWithGasMeter(m.GasMeter), m).String() + "\n" + e.Stacktrace.String() } func (e *Exception) Sprint(m *Machine) string { - res := e.Value.Sprint(m) + res := e.Value.Sprint(NewStringBuilderWithGasMeter(m.GasMeter), m).String() return res } diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index aef04da51a6..b3131233484 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -1082,6 +1082,7 @@ const ( OpRangeIterArrayPtr Op = 0xD6 OpReturnCallDefers Op = 0xD7 // XXX rename to OpCallDefers OpVoid Op = 0xFF // For profiling simple operation + ) const GasFactorCPU int64 = 1 @@ -1217,6 +1218,8 @@ const ( OpCPURangeIterMap = 48 OpCPURangeIterArrayPtr = 46 OpCPUReturnCallDefers = 78 + + OpCharPrint = 100 ) //---------------------------------------- @@ -2448,7 +2451,7 @@ func (m *Machine) String() string { // print the pkgpath. fmt.Fprintf(builder, " %s(%d) %s\n", gens, gen, pv.PkgPath) } else { - bsi := b.StringIndented(" ") + bsi := b.StringIndented(NewStringBuilderWithGasMeter(m.GasMeter), " ") fmt.Fprintf(builder, " %s(%d) %s\n", gens, gen, bsi) } // Update b @@ -2474,7 +2477,7 @@ func (m *Machine) String() string { break // done, skip *PackageNode. } else { fmt.Fprintf(builder, " #%d %s\n", i, - b.StringIndented(" ")) + b.StringIndented(NewStringBuilderWithGasMeter(m.GasMeter), " ")) } } builder.WriteString(" Frames:\n") diff --git a/gnovm/pkg/gnolang/op_expressions.go b/gnovm/pkg/gnolang/op_expressions.go index 74d6d7c51ef..09f83b5c560 100644 --- a/gnovm/pkg/gnolang/op_expressions.go +++ b/gnovm/pkg/gnolang/op_expressions.go @@ -213,8 +213,8 @@ func (m *Machine) doOpTypeAssert1() { // TODO: default panic type? ex := fmt.Sprintf( "non-concrete %s doesn't implement %s", - xt.String(), - it.String()) + xt.WriteString(NewStringBuilderWithGasMeter(m.GasMeter)), + it.WriteString(NewStringBuilderWithGasMeter(m.GasMeter))) m.pushPanic(typedString(ex)) return } @@ -226,8 +226,8 @@ func (m *Machine) doOpTypeAssert1() { // TODO: default panic type? ex := fmt.Sprintf( "%s doesn't implement %s (%s)", - xt.String(), - it.String(), + xt.WriteString(NewStringBuilderWithGasMeter(m.GasMeter)), + it.WriteString(NewStringBuilderWithGasMeter(m.GasMeter)), err.Error()) m.pushPanic(typedString(ex)) return @@ -240,7 +240,7 @@ func (m *Machine) doOpTypeAssert1() { } } else { // is concrete assert if xt == nil { - ex := fmt.Sprintf("nil is not of type %s", t.String()) + ex := fmt.Sprintf("nil is not of type %s", t.WriteString(NewStringBuilderWithGasMeter(m.GasMeter))) m.pushPanic(typedString(ex)) return } @@ -253,8 +253,8 @@ func (m *Machine) doOpTypeAssert1() { // TODO: default panic type? ex := fmt.Sprintf( "%s is not of type %s", - xt.String(), - t.String()) + xt.WriteString(NewStringBuilderWithGasMeter(m.GasMeter)), + t.WriteString(NewStringBuilderWithGasMeter(m.GasMeter))) m.pushPanic(typedString(ex)) return } diff --git a/gnovm/pkg/gnolang/types.go b/gnovm/pkg/gnolang/types.go index 670d8f1e75a..789f4fec4cc 100644 --- a/gnovm/pkg/gnolang/types.go +++ b/gnovm/pkg/gnolang/types.go @@ -23,7 +23,8 @@ type Type interface { Kind() Kind // penetrates *DeclaredType TypeID() TypeID // deterministic String() string // for dev/debugging - Elem() Type // for TODO... types + WriteString(builder Builder) Builder + Elem() Type // for TODO... types GetPkgPath() string IsNamed() bool // named vs unname type. property as a method IsImmutable() bool // immutable types @@ -275,51 +276,76 @@ func (pt PrimitiveType) TypeID() TypeID { panic(fmt.Sprintf("unexpected primitive type %v", pt)) } } - func (pt PrimitiveType) String() string { + builder := strings.Builder{} + return pt.WriteString(&builder).String() +} + +func (pt PrimitiveType) WriteString(builder Builder) Builder { switch pt { case InvalidType: - return string("") + builder.WriteString("") + return builder case UntypedBoolType: - return string(" bool") + builder.WriteString(" bool") + return builder case BoolType: - return string("bool") + builder.WriteString("bool") + return builder case UntypedStringType: - return string(" string") + builder.WriteString(" string") + return builder case StringType: - return string("string") + builder.WriteString("string") + return builder case IntType: - return string("int") + builder.WriteString("int") + return builder case Int8Type: - return string("int8") + builder.WriteString("int8") + return builder case Int16Type: - return string("int16") + builder.WriteString("int16") + return builder case UntypedRuneType: - return string(" int32") + builder.WriteString(" int32") + return builder case Int32Type: - return string("int32") + builder.WriteString("int32") + return builder case Int64Type: - return string("int64") + builder.WriteString("int64") + return builder case UintType: - return string("uint") + builder.WriteString("uint") + return builder case Uint8Type: - return string("uint8") + builder.WriteString("uint8") + return builder case DataByteType: - return string(" uint8") + builder.WriteString(" uint8") + return builder case Uint16Type: - return string("uint16") + builder.WriteString("uint16") + return builder case Uint32Type: - return string("uint32") + builder.WriteString("uint32") + return builder case Uint64Type: - return string("uint64") + builder.WriteString("uint64") + return builder case Float32Type: - return string("float32") + builder.WriteString("float32") + return builder case Float64Type: - return string("float64") + builder.WriteString("float64") + return builder case UntypedBigintType: - return string(" bigint") + builder.WriteString(" bigint") + return builder case UntypedBigdecType: - return string(" bigdec") + builder.WriteString(" bigdec") + return builder default: panic(fmt.Sprintf("unexpected primitive type %d", pt)) } @@ -369,14 +395,26 @@ func (ft FieldType) TypeID() TypeID { } func (ft FieldType) String() string { + builder := strings.Builder{} + return ft.WriteString(&builder).String() +} + +func (ft FieldType) WriteString(builder Builder) Builder { tag := "" if ft.Tag != "" { tag = " " + strconv.Quote(string(ft.Tag)) } if ft.Name == "" { - return fmt.Sprintf("(embedded) %s%s", ft.Type.String(), tag) + builder.WriteString("(embedded) ") + ft.Type.WriteString(builder) + builder.WriteString(tag) + return builder } else { - return fmt.Sprintf("%s %s%s", ft.Name, ft.Type.String(), tag) + builder.WriteString(string(ft.Name)) + builder.WriteString(" ") + ft.Type.WriteString(builder) + builder.WriteString(tag) + return builder } } @@ -472,29 +510,33 @@ func (l FieldTypeList) HasUnexported() bool { } func (l FieldTypeList) String() string { - return l.string(true, "; ") + var builder strings.Builder + return l.WriteString(&builder).String() } -// StringForFunc returns a list of the fields, suitable for functions. +func (l FieldTypeList) WriteString(builder Builder) Builder { + return l.string(builder, true, "; ") +} + +// WriteStringForFunc returns a list of the fields, suitable for functions. // Compared to [FieldTypeList.String], this method does not return field names, // and separates the fields using ", ". -func (l FieldTypeList) StringForFunc() string { - return l.string(false, ", ") +func (l FieldTypeList) WriteStringForFunc(builder Builder) Builder { + return l.string(builder, false, ", ") } -func (l FieldTypeList) string(withName bool, sep string) string { - var bld strings.Builder +func (l FieldTypeList) string(builder Builder, withName bool, sep string) Builder { for i, ft := range l { if i != 0 { - bld.WriteString(sep) + builder.WriteString(sep) } if withName { - bld.WriteString(string(ft.Name)) - bld.WriteByte(' ') + builder.WriteString(string(ft.Name)) + builder.WriteByte(' ') } - bld.WriteString(ft.Type.String()) + ft.Type.WriteString(builder) } - return bld.String() + return builder } // Like TypeID() but without considering field names; @@ -542,7 +584,16 @@ func (at *ArrayType) TypeID() TypeID { } func (at *ArrayType) String() string { - return fmt.Sprintf("[%d]%s", at.Len, at.Elt.String()) + builder := strings.Builder{} + return at.WriteString(&builder).String() +} + +func (at *ArrayType) WriteString(builder Builder) Builder { + builder.WriteString("[") + builder.WriteString(strconv.Itoa(at.Len)) + builder.WriteByte(']') + at.Elt.WriteString(builder) + return builder } func (at *ArrayType) Elem() Type { @@ -583,12 +634,20 @@ func (st *SliceType) TypeID() TypeID { } return st.typeid } - func (st *SliceType) String() string { + builder := strings.Builder{} + return st.WriteString(&builder).String() +} + +func (st *SliceType) WriteString(builder Builder) Builder { if st.Vrd { - return fmt.Sprintf("...%s", st.Elt.String()) + builder.WriteString("...") + st.Elt.WriteString(builder) + return builder } else { - return fmt.Sprintf("[]%s", st.Elt.String()) + builder.WriteString("[]") + st.Elt.WriteString(builder) + return builder } } @@ -625,12 +684,18 @@ func (pt *PointerType) TypeID() TypeID { } func (pt *PointerType) String() string { + builder := strings.Builder{} + return pt.WriteString(&builder).String() +} +func (pt *PointerType) WriteString(builder Builder) Builder { if pt == nil { panic("invalid nil pointer type") } else if pt.Elt == nil { panic("invalid nil pointer element type") } else { - return fmt.Sprintf("*%v", pt.Elt) + builder.WriteByte('*') + pt.Elt.WriteString(builder) + return builder } } @@ -764,8 +829,15 @@ func (st *StructType) TypeID() TypeID { } func (st *StructType) String() string { - return fmt.Sprintf("struct{%s}", - FieldTypeList(st.Fields).String()) + builder := strings.Builder{} + return st.WriteString(&builder).String() +} + +func (st *StructType) WriteString(builder Builder) Builder { + builder.WriteString("struct{") + FieldTypeList(st.Fields).WriteString(builder) + builder.WriteByte('}') + return builder } func (st *StructType) Elem() Type { @@ -885,7 +957,14 @@ func (pt *PackageType) TypeID() TypeID { } func (pt *PackageType) String() string { - return "package{}" + builder := strings.Builder{} + return pt.WriteString(&builder).String() +} + +func (pt *PackageType) WriteString(builder Builder) Builder { + builder.WriteString("package{}") + + return builder } func (pt *PackageType) Elem() Type { @@ -951,13 +1030,22 @@ func (it *InterfaceType) GetMethodFieldType(mname Name) *FieldType { } func (it *InterfaceType) String() string { + builder := strings.Builder{} + return it.WriteString(&builder).String() +} + +func (it *InterfaceType) WriteString(builder Builder) Builder { if it.Generic != "" { - return fmt.Sprintf("<%s>{%s}", - it.Generic, - FieldTypeList(it.Methods).String()) + builder.WriteString("<") + builder.WriteString(string(it.Generic)) + builder.WriteString(">{") + FieldTypeList(it.Methods).WriteString(builder) + return builder } else { - return fmt.Sprintf("interface {%s}", - FieldTypeList(it.Methods).String()) + builder.WriteString("interface {") + FieldTypeList(it.Methods).WriteString(builder) + builder.WriteByte('}') + return builder } } @@ -1094,13 +1182,24 @@ func (ct *ChanType) TypeID() TypeID { } func (ct *ChanType) String() string { + builder := strings.Builder{} + return ct.WriteString(&builder).String() +} + +func (ct *ChanType) WriteString(builder Builder) Builder { switch ct.Dir { case SEND | RECV: - return "chan " + ct.Elt.String() + builder.WriteString("chan ") + ct.Elt.WriteString(builder) + return builder case SEND: - return "<-chan " + ct.Elt.String() + builder.WriteString("<-chan ") + ct.Elt.WriteString(builder) + return builder case RECV: - return "chan<- " + ct.Elt.String() + builder.WriteString("chan<- ") + ct.Elt.WriteString(builder) + return builder default: panic("should not happen") } @@ -1289,20 +1388,31 @@ func (ft *FuncType) TypeID() TypeID { } func (ft *FuncType) String() string { + builder := strings.Builder{} + return ft.WriteString(&builder).String() +} + +func (ft *FuncType) WriteString(builder Builder) Builder { switch len(ft.Results) { case 0: // XXX add ->() - return fmt.Sprintf("func(%s)", FieldTypeList(ft.Params).StringForFunc()) + builder.WriteString("func(") + FieldTypeList(ft.Params).WriteStringForFunc(builder) + builder.WriteByte(')') + return builder case 1: - // XXX add ->() - return fmt.Sprintf("func(%s) %s", - FieldTypeList(ft.Params).StringForFunc(), - ft.Results[0].Type.String()) + builder.WriteString("func(") + FieldTypeList(ft.Params).WriteStringForFunc(builder) + builder.WriteString(") ") + ft.Results[0].Type.WriteString(builder) + return builder default: - // XXX make ()->() - return fmt.Sprintf("func(%s) (%s)", - FieldTypeList(ft.Params).StringForFunc(), - FieldTypeList(ft.Results).StringForFunc()) + builder.WriteString("func(") + FieldTypeList(ft.Params).WriteStringForFunc(builder) + builder.WriteString(") (") + FieldTypeList(ft.Results).WriteStringForFunc(builder) + builder.WriteByte(')') + return builder } } @@ -1366,9 +1476,16 @@ func (mt *MapType) TypeID() TypeID { } func (mt *MapType) String() string { - return fmt.Sprintf("map[%s]%s", - mt.Key.String(), - mt.Value.String()) + builder := strings.Builder{} + return mt.WriteString(&builder).String() +} + +func (mt *MapType) WriteString(builder Builder) Builder { + builder.WriteString("map[") + mt.Key.WriteString(builder) + builder.WriteByte(']') + mt.Value.WriteString(builder) + return builder } func (mt *MapType) Elem() Type { @@ -1400,7 +1517,13 @@ func (tt *TypeType) TypeID() TypeID { } func (tt *TypeType) String() string { - return string("type{}") + builder := strings.Builder{} + return tt.WriteString(&builder).String() +} + +func (tt *TypeType) WriteString(builder Builder) Builder { + builder.WriteString("type{}") + return builder } func (tt *TypeType) Elem() Type { @@ -1529,10 +1652,17 @@ func DeclaredTypeID(pkgPath string, loc Location, name Name) TypeID { } func (dt *DeclaredType) String() string { + builder := strings.Builder{} + return dt.WriteString(&builder).String() +} + +func (dt *DeclaredType) WriteString(builder Builder) Builder { if dt.ParentLoc.IsZero() { - return fmt.Sprintf("%s.%s", dt.PkgPath, dt.Name) + builder.WriteString(fmt.Sprintf("%s.%s", dt.PkgPath, dt.Name)) + return builder } else { - return fmt.Sprintf("%s[%s].%s", dt.PkgPath, dt.ParentLoc.String(), dt.Name) + builder.WriteString(fmt.Sprintf("%s[%s].%s", dt.PkgPath, dt.ParentLoc.String(), dt.Name)) + return builder } } @@ -1774,7 +1904,13 @@ func (bt blockType) TypeID() TypeID { } func (bt blockType) String() string { - return "block" + builder := strings.Builder{} + return bt.WriteString(&builder).String() +} + +func (bt blockType) WriteString(builder Builder) Builder { + builder.WriteString("block") + return builder } func (bt blockType) Elem() Type { @@ -1803,7 +1939,14 @@ func (bt heapItemType) TypeID() TypeID { } func (bt heapItemType) String() string { - return "heapitem" + builder := strings.Builder{} + return bt.WriteString(&builder).String() +} + +func (bt heapItemType) WriteString(builder Builder) Builder { + builder.WriteString("heapitem") + + return builder } func (bt heapItemType) Elem() Type { @@ -1848,16 +1991,22 @@ func (tt *tupleType) TypeID() TypeID { } func (tt *tupleType) String() string { + builder := strings.Builder{} + return tt.WriteString(&builder).String() +} + +func (tt *tupleType) WriteString(builder Builder) Builder { ell := len(tt.Elts) - s := "(" + + builder.WriteByte('(') for i, et := range tt.Elts { - s += et.String() + et.WriteString(builder) if i != ell-1 { - s += "," + builder.WriteByte(',') } } - s += ")" - return s + builder.WriteByte(')') + return builder } func (tt *tupleType) Elem() Type { @@ -1888,7 +2037,14 @@ func (rt RefType) TypeID() TypeID { } func (rt RefType) String() string { - return fmt.Sprintf("RefType{%v}", rt.ID) + builder := strings.Builder{} + return rt.WriteString(&builder).String() +} + +func (rt RefType) WriteString(builder Builder) Builder { + builder.WriteString(fmt.Sprintf("RefType{%v}", rt.ID)) + + return builder } func (rt RefType) Elem() Type { diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go index a8835fb67d5..e74dc2b40d2 100644 --- a/gnovm/pkg/gnolang/uverse.go +++ b/gnovm/pkg/gnolang/uverse.go @@ -1132,7 +1132,7 @@ func uversePrint(m *Machine, xv PointerValue, newline bool) { } case 1: ev := xv.TV.GetPointerAtIndexInt(m.Store, 0).Deref() - res := ev.Sprint(m) + res := ev.Sprint(NewStringBuilderWithGasMeter(m.GasMeter), m).String() io.WriteString(m.Output, res) if newline { m.Output.Write(bNewline) @@ -1144,7 +1144,7 @@ func uversePrint(m *Machine, xv PointerValue, newline bool) { buf.WriteByte(' ') } ev := xv.TV.GetPointerAtIndexInt(m.Store, i).Deref() - buf.WriteString(ev.Sprint(m)) + buf.WriteString(ev.Sprint(NewStringBuilderWithGasMeter(m.GasMeter), m).String()) } if newline { buf.WriteByte('\n') diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 04c0ce9166f..279cec87c98 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -15,12 +15,19 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto" ) +type Builder interface { + WriteString(s string) (int, error) + WriteByte(c byte) error + String() string +} + // ---------------------------------------- // (runtime) Value type Value interface { assertValue() String() string // for debugging + WriteString(builder Builder) Builder // DeepFill returns the same value, filled. // diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index ab36c0c1e47..99208be9462 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -6,10 +6,40 @@ import ( "reflect" "strconv" "strings" + + "github.com/gnolang/gno/tm2/pkg/overflow" + "github.com/gnolang/gno/tm2/pkg/store" ) +type StringBuilderWithGasMeter struct { + GasMeter store.GasMeter + strings.Builder +} + +func NewStringBuilderWithGasMeter(gasMeter store.GasMeter) *StringBuilderWithGasMeter { + return &StringBuilderWithGasMeter{ + GasMeter: gasMeter, + } +} + +func (p *StringBuilderWithGasMeter) incrCPU(size int64) { + if p == nil { + return + } + + if p.GasMeter != nil { + gasCPU := overflow.Mulp(size, GasFactorCPU) + p.GasMeter.ConsumeGas(gasCPU, "") + } +} + +func (p *StringBuilderWithGasMeter) WriteString(s string) (int, error) { + p.incrCPU(int64(OpCharPrint * len(s))) + return p.Builder.WriteString(s) +} + type protectedStringer interface { - ProtectedString(*seenValues) string + ProtectedString(Builder, *seenValues) Builder } const ( @@ -60,68 +90,122 @@ func newSeenValues() *seenValues { } func (sv StringValue) String() string { - return strconv.Quote(string(sv)) + builder := strings.Builder{} + sv.WriteString(&builder) + return builder.String() +} + +func (sv StringValue) WriteString(builder Builder) Builder { + builder.WriteString(strconv.Quote(string(sv))) + return builder } func (biv BigintValue) String() string { - return biv.V.String() + builder := strings.Builder{} + biv.WriteString(&builder) + return builder.String() +} + +func (biv BigintValue) WriteString(builder Builder) Builder { + builder.WriteString(biv.V.String()) + return builder } func (bdv BigdecValue) String() string { - return bdv.V.String() + builder := strings.Builder{} + bdv.WriteString(&builder) + return builder.String() +} + +func (bdv BigdecValue) WriteString(builder Builder) Builder { + builder.WriteString(bdv.V.String()) + return builder } func (dbv DataByteValue) String() string { - return fmt.Sprintf("(%0X)", (dbv.GetByte())) + builder := strings.Builder{} + dbv.WriteString(&builder) + return builder.String() +} + +func (dbv DataByteValue) WriteString(builder Builder) Builder { + builder.WriteString(fmt.Sprintf("(%0X)", (dbv.GetByte()))) + return builder } func (av *ArrayValue) String() string { - return av.ProtectedString(newSeenValues()) + builder := strings.Builder{} + av.WriteString(&builder) + return builder.String() } -func (av *ArrayValue) ProtectedString(seen *seenValues) string { +func (av *ArrayValue) WriteString(builder Builder) Builder { + return av.ProtectedString(builder, newSeenValues()) +} + +func (av *ArrayValue) ProtectedString(builder Builder, seen *seenValues) Builder { if i := seen.IndexOf(av); i != -1 { - return fmt.Sprintf("ref@%d", i) + builder.WriteString(fmt.Sprintf("ref@%d", i)) + return builder } seen.nc-- if seen.nc < 0 { - return "..." + builder.WriteString("...") + return builder } seen.Put(av) defer seen.Pop() - ss := make([]string, len(av.List)) if av.Data == nil { + builder.WriteString("array[") for i, e := range av.List { - ss[i] = e.ProtectedString(seen) + e.ProtectedString(builder, seen) + if i < len(av.List)-1 { + builder.WriteString(",") + } } + builder.WriteString("]") // NOTE: we may want to unify the representation, // but for now tests expect this to be different. // This may be helpful for testing implementation behavior. - return "array[" + strings.Join(ss, ",") + "]" + return builder } if len(av.Data) > 256 { - return fmt.Sprintf("array[0x%X...]", av.Data[:256]) + builder.WriteString(fmt.Sprintf("array[0x%X...]", av.Data[:256])) + + return builder } - return fmt.Sprintf("array[0x%X]", av.Data) + builder.WriteString(fmt.Sprintf("array[0x%X]", av.Data)) + return builder } func (sv *SliceValue) String() string { - return sv.ProtectedString(newSeenValues()) + builder := strings.Builder{} + sv.WriteString(&builder) + return builder.String() +} + +func (sv *SliceValue) WriteString(builder Builder) Builder { + return sv.ProtectedString(builder, newSeenValues()) } -func (sv *SliceValue) ProtectedString(seen *seenValues) string { +func (sv *SliceValue) ProtectedString(builder Builder, seen *seenValues) Builder { if sv.Base == nil { - return "nil-slice" + builder.WriteString(fmt.Sprint("nil-slice")) + return builder } if i := seen.IndexOf(sv); i != -1 { - return fmt.Sprintf("ref@%d", i) + builder.WriteString(fmt.Sprintf("ref@%d", i)) + return builder } if ref, ok := sv.Base.(RefValue); ok { - return fmt.Sprintf("slice[%v]", ref) + builder.WriteString("slice[") + ref.WriteString(builder) + builder.WriteString("]") + return builder } seen.Put(sv) @@ -129,25 +213,41 @@ func (sv *SliceValue) ProtectedString(seen *seenValues) string { vbase := sv.Base.(*ArrayValue) if vbase.Data == nil { - ss := make([]string, sv.Length) + builder.WriteString("slice[") for i, e := range vbase.List[sv.Offset : sv.Offset+sv.Length] { - ss[i] = e.ProtectedString(seen) + e.ProtectedString(builder, seen) + if i < sv.Length-1 { + builder.WriteString(",") + } } - return "slice[" + strings.Join(ss, ",") + "]" + builder.WriteString("]") + + return builder } if sv.Length > 256 { - return fmt.Sprintf("slice[0x%X...(%d)]", vbase.Data[sv.Offset:sv.Offset+256], sv.Length) + builder.WriteString(fmt.Sprintf("slice[0x%X...(%d)]", vbase.Data[sv.Offset:sv.Offset+256], sv.Length)) + + return builder } - return fmt.Sprintf("slice[0x%X]", vbase.Data[sv.Offset:sv.Offset+sv.Length]) + builder.WriteString(fmt.Sprintf("slice[0x%X]", vbase.Data[sv.Offset:sv.Offset+sv.Length])) + + return builder } func (pv PointerValue) String() string { - return pv.ProtectedString(newSeenValues()) + builder := strings.Builder{} + pv.WriteString(&builder) + return builder.String() +} + +func (pv PointerValue) WriteString(builder Builder) Builder { + return pv.ProtectedString(builder, newSeenValues()) } -func (pv PointerValue) ProtectedString(seen *seenValues) string { +func (pv PointerValue) ProtectedString(builder Builder, seen *seenValues) Builder { if i := seen.IndexOf(pv); i != -1 { - return fmt.Sprintf("ref@%d", i) + builder.WriteString(fmt.Sprintf("ref@%d", i)) + return builder } seen.Put(pv) @@ -155,184 +255,276 @@ func (pv PointerValue) ProtectedString(seen *seenValues) string { // Handle nil TV's, avoiding a nil pointer deref below. if pv.TV == nil { - return "&" + builder.WriteString("&") + return builder } - return fmt.Sprintf("&%s", pv.TV.ProtectedString(seen)) + builder.WriteString("&") + pv.TV.ProtectedString(builder, seen) + return builder } func (sv *StructValue) String() string { - return sv.ProtectedString(newSeenValues()) + builder := strings.Builder{} + sv.WriteString(&builder) + return builder.String() } -func (sv *StructValue) ProtectedString(seen *seenValues) string { +func (sv *StructValue) WriteString(builder Builder) Builder { + return sv.ProtectedString(builder, newSeenValues()) +} + +func (sv *StructValue) ProtectedString(builder Builder, seen *seenValues) Builder { if i := seen.IndexOf(sv); i != -1 { - return fmt.Sprintf("ref@%d", i) + builder.WriteString(fmt.Sprintf("ref@%d", i)) + + return builder } seen.Put(sv) defer seen.Pop() - ss := make([]string, len(sv.Fields)) + builder.WriteString("struct{") for i, f := range sv.Fields { - ss[i] = f.ProtectedString(seen) + f.ProtectedString(builder, seen) + if i < len(sv.Fields)-1 { + builder.WriteString(",") + } } - return "struct{" + strings.Join(ss, ",") + "}" + builder.WriteString("}") + + return builder } func (fv *FuncValue) String() string { + builder := strings.Builder{} + fv.WriteString(&builder) + return builder.String() +} + +func (fv *FuncValue) WriteString(builder Builder) Builder { name := string(fv.Name) if fv.Type == nil { - return fmt.Sprintf("incomplete-func ?%s(?)?", name) + builder.WriteString(fmt.Sprintf("incomplete-func ?%s(?)?", name)) + return builder } if name == "" { - return fmt.Sprintf("%s{...}", fv.Type.String()) + fv.Type.WriteString(builder) + builder.WriteString("{...}") + return builder } - return name + + builder.WriteString(name) + + return builder } func (bmv *BoundMethodValue) String() string { + var builder strings.Builder + return bmv.WriteString(&builder).String() +} + +func (bmv *BoundMethodValue) WriteString(builder Builder) Builder { name := bmv.Func.Name - var ( - recvT string = "?" - params string = "?" - results string = "(?)" - ) + if ft, ok := bmv.Func.Type.(*FuncType); ok { - recvT = ft.Params[0].Type.String() - params = FieldTypeList(ft.Params).StringForFunc() - if len(results) > 0 { - results = FieldTypeList(ft.Results).StringForFunc() - results = "(" + results + ")" - } + builder.WriteString("<") + ft.Params[0].Type.WriteString(builder) + builder.WriteString(">.") + builder.WriteString(string(name) + "(") + FieldTypeList(ft.Params).WriteStringForFunc(builder) + builder.WriteString(")") + + builder.WriteString("(") + FieldTypeList(ft.Results).WriteStringForFunc(builder) + builder.WriteString(")") + + return builder } - return fmt.Sprintf("<%s>.%s(%s)%s", - recvT, name, params, results) + + builder.WriteString(fmt.Sprintf(".?%s(?)(?)", + name)) + return builder } func (mv *MapValue) String() string { - return mv.ProtectedString(newSeenValues()) + builder := strings.Builder{} + mv.WriteString(&builder) + return builder.String() +} + +func (mv *MapValue) WriteString(builder Builder) Builder { + return mv.ProtectedString(builder, newSeenValues()) } -func (mv *MapValue) ProtectedString(seen *seenValues) string { +func (mv *MapValue) ProtectedString(builder Builder, seen *seenValues) Builder { if mv.List == nil { - return "zero-map" + builder.WriteString("zero-map") + return builder } if i := seen.IndexOf(mv); i != -1 { - return fmt.Sprintf("ref@%d", i) + builder.WriteString(fmt.Sprintf("ref@%d", i)) + return builder } seen.Put(mv) defer seen.Pop() - ss := make([]string, 0, mv.GetLength()) + builder.WriteString("map{") next := mv.List.Head for next != nil { - ss = append(ss, - next.Key.ProtectedString(seen)+":"+ - next.Value.ProtectedString(seen)) + next.Key.ProtectedString(builder, seen) + builder.WriteString(":") + next.Value.ProtectedString(builder, seen) next = next.Next + if next != nil { + builder.WriteString(",") + } } - return "map{" + strings.Join(ss, ",") + "}" + builder.WriteString("}") + return builder } func (tv TypeValue) String() string { - return fmt.Sprintf("typeval{%s}", - tv.Type.String()) + builder := strings.Builder{} + tv.WriteString(&builder) + return builder.String() +} + +func (tv TypeValue) WriteString(builder Builder) Builder { + builder.WriteString("typeval{") + tv.Type.WriteString(builder) + builder.WriteString("}") + + return builder } func (pv *PackageValue) String() string { - return fmt.Sprintf("package(%s %s)", pv.PkgName, pv.PkgPath) + builder := strings.Builder{} + pv.WriteString(&builder) + return builder.String() +} + +func (pv *PackageValue) WriteString(builder Builder) Builder { + builder.WriteString(fmt.Sprintf("package(%s %s)", pv.PkgName, pv.PkgPath)) + + return builder } func (b *Block) String() string { - return b.StringIndented(" ") + builder := strings.Builder{} + b.WriteString(&builder) + return builder.String() +} + +func (b *Block) WriteString(builder Builder) Builder { + return b.StringIndented(builder, " ") } -func (b *Block) StringIndented(indent string) string { +func (b *Block) StringIndented(builder Builder, indent string) Builder { source := toString(b.Source) if len(source) > 32 { source = source[:32] + "..." } - lines := make([]string, 0, 3) - lines = append(lines, - fmt.Sprintf("Block(ID:%v,Addr:%p,Source:%s,Parent:%p)", - b.ObjectInfo.ID, b, source, b.Parent)) // XXX Parent may be RefValue{}. + builder.WriteString(fmt.Sprintf("%sBlock(ID:%v,Addr:%p,Source:%s,Parent:%p)", + indent, b.ObjectInfo.ID, b, source, b.Parent)) // XXX Parent may be RefValue{}. if b.Source != nil { if _, ok := b.Source.(RefNode); ok { - lines = append(lines, - fmt.Sprintf("%s(RefNode names not shown)", indent)) + builder.WriteString(fmt.Sprintf("\n%s(RefNode names not shown)", indent)) } else { types := b.Source.GetStaticBlock().Types for i, n := range b.Source.GetBlockNames() { if len(b.Values) <= i { - lines = append(lines, - fmt.Sprintf("%s%s: undefined static:%s", indent, n, types[i])) + builder.WriteString(fmt.Sprintf("\n%s%s: undefined static:%s", indent, n, types[i].WriteString(builder))) } else { - lines = append(lines, - fmt.Sprintf("%s%s: %s static:%s", - indent, n, b.Values[i].String(), types[i])) + builder.WriteString(fmt.Sprintf("\n%s%s: %s static:%s", + indent, n, b.Values[i].WriteString(builder), types[i].WriteString(builder))) } } } } - return strings.Join(lines, "\n") + return builder } func (rv RefValue) String() string { + builder := strings.Builder{} + rv.WriteString(&builder) + return builder.String() +} + +func (rv RefValue) WriteString(builder Builder) Builder { if rv.PkgPath == "" { - return fmt.Sprintf("ref(%v)", - rv.ObjectID) + builder.WriteString(fmt.Sprintf("ref(%v)", + rv.ObjectID)) + + return builder } - return fmt.Sprintf("ref(%s)", - rv.PkgPath) + + builder.WriteString(fmt.Sprintf("ref(%s)", + rv.PkgPath)) + + return builder } func (hiv *HeapItemValue) String() string { - return fmt.Sprintf("heapitem(%v)", - hiv.Value) + builder := strings.Builder{} + hiv.WriteString(&builder) + return builder.String() +} + +func (hiv *HeapItemValue) WriteString(builder Builder) Builder { + builder.WriteString("heapitem(") + hiv.Value.WriteString(builder) + builder.WriteString(")") + return builder } // ---------------------------------------- // *TypedValue.Sprint // for print() and println(). -func (tv *TypedValue) Sprint(m *Machine) string { +func (tv *TypedValue) Sprint(builder Builder, m *Machine) Builder { // if undefined, just "undefined". if tv == nil || tv.T == nil { - return undefinedStr + builder.WriteString(undefinedStr) + return builder } // if implements .String(), return it. if IsImplementedBy(gStringerType, tv.T) && !tv.IsNilInterface() { res := m.Eval(Call(Sel(&ConstExpr{TypedValue: *tv}, "String"))) - return res[0].GetString() + builder.WriteString(res[0].GetString()) + + return builder } // if implements .Error(), return it. if IsImplementedBy(gErrorType, tv.T) { res := m.Eval(Call(Sel(&ConstExpr{TypedValue: *tv}, "Error"))) - return res[0].GetString() + builder.WriteString(res[0].GetString()) + return builder } - return tv.ProtectedSprint(newSeenValues(), true) + return tv.ProtectedSprint(builder, newSeenValues(), true) } -func (tv *TypedValue) ProtectedSprint(seen *seenValues, considerDeclaredType bool) string { +func (tv *TypedValue) ProtectedSprint(builder Builder, seen *seenValues, considerDeclaredType bool) Builder { if i := seen.IndexOf(tv.V); i != -1 { - return fmt.Sprintf("ref@%d", i) + builder.WriteString(fmt.Sprintf("ref@%d", i)) + return builder } // print declared type if _, ok := tv.T.(*DeclaredType); ok && considerDeclaredType { - return tv.ProtectedString(seen) + return tv.ProtectedString(builder, seen) } // This is a special case that became necessary after adding `ProtectedString()` methods to // reliably prevent recursive print loops. if tv.V != nil { if v, ok := tv.V.(RefValue); ok { - return v.String() + v.WriteString(builder) + return builder } } @@ -341,58 +533,96 @@ func (tv *TypedValue) ProtectedSprint(seen *seenValues, considerDeclaredType boo case PrimitiveType: switch bt { case UntypedBoolType, BoolType: - return fmt.Sprintf("%t", tv.GetBool()) + builder.WriteString(fmt.Sprintf("%t", tv.GetBool())) + + return builder case UntypedStringType, StringType: - return tv.GetString() + builder.WriteString(tv.GetString()) + + return builder case IntType: - return fmt.Sprintf("%d", tv.GetInt()) + builder.WriteString(fmt.Sprintf("%d", tv.GetInt())) + + return builder case Int8Type: - return fmt.Sprintf("%d", tv.GetInt8()) + builder.WriteString(fmt.Sprintf("%d", tv.GetInt8())) + + return builder case Int16Type: - return fmt.Sprintf("%d", tv.GetInt16()) + builder.WriteString(fmt.Sprintf("%d", tv.GetInt16())) + + return builder case UntypedRuneType, Int32Type: - return fmt.Sprintf("%d", tv.GetInt32()) + builder.WriteString(fmt.Sprintf("%d", tv.GetInt32())) + + return builder case Int64Type: - return fmt.Sprintf("%d", tv.GetInt64()) + builder.WriteString(fmt.Sprintf("%d", tv.GetInt64())) + + return builder case UintType: - return fmt.Sprintf("%d", tv.GetUint()) + builder.WriteString(fmt.Sprintf("%d", tv.GetUint())) + return builder case Uint8Type: - return fmt.Sprintf("%d", tv.GetUint8()) + builder.WriteString(fmt.Sprintf("%d", tv.GetUint8())) + return builder case DataByteType: - return fmt.Sprintf("%d", tv.GetDataByte()) + builder.WriteString(fmt.Sprintf("%d", tv.GetDataByte())) + + return builder case Uint16Type: - return fmt.Sprintf("%d", tv.GetUint16()) + builder.WriteString(fmt.Sprintf("%d", tv.GetUint16())) + + return builder case Uint32Type: - return fmt.Sprintf("%d", tv.GetUint32()) + builder.WriteString(fmt.Sprintf("%d", tv.GetUint32())) + + return builder case Uint64Type: - return fmt.Sprintf("%d", tv.GetUint64()) + builder.WriteString(fmt.Sprintf("%d", tv.GetUint64())) + + return builder case Float32Type: - return fmt.Sprintf("%v", math.Float32frombits(tv.GetFloat32())) + builder.WriteString(fmt.Sprintf("%v", math.Float32frombits(tv.GetFloat32()))) + return builder case Float64Type: - return fmt.Sprintf("%v", math.Float64frombits(tv.GetFloat64())) + builder.WriteString(fmt.Sprintf("%v", math.Float64frombits(tv.GetFloat64()))) + return builder case UntypedBigintType: - return tv.V.(BigintValue).V.String() + builder.WriteString(tv.V.(BigintValue).V.String()) + + return builder case UntypedBigdecType: - return tv.V.(BigdecValue).V.String() + builder.WriteString(tv.V.(BigdecValue).V.String()) + + return builder default: panic("should not happen") } case *PointerType: if tv.V == nil { - return "typed-nil" + builder.WriteString("typed-nil") + + return builder } roPre, roPost := "", "" if tv.IsReadonly() { roPre, roPost = "readonly(", ")" } - return roPre + tv.V.(PointerValue).ProtectedString(seen) + roPost + builder.WriteString(roPre) + tv.V.(PointerValue).ProtectedString(builder, seen) + builder.WriteString(roPost) + return builder case *FuncType: switch fv := tv.V.(type) { case nil: - ft := tv.T.String() - return nilStr + " " + ft + builder.WriteString(nilStr + " ") + tv.T.WriteString(builder) + return builder case *FuncValue, *BoundMethodValue: - return fv.String() + fv.WriteString(builder) + + return builder default: panic(fmt.Sprintf( "unexpected func type %v", @@ -404,19 +634,28 @@ func (tv *TypedValue) ProtectedSprint(seen *seenValues, considerDeclaredType boo panic("should not happen") } } - return nilStr + builder.WriteString(nilStr) + + return builder case *DeclaredType: panic("should not happen") case *PackageType: - return tv.V.(*PackageValue).String() + tv.V.(*PackageValue).WriteString(builder) + + return builder case *ChanType: panic("not yet implemented") case *TypeType: - return tv.V.(TypeValue).String() + tv.V.(TypeValue).WriteString(builder) + + return builder default: // The remaining types may have a nil value. if tv.V == nil { - return "(" + nilStr + " " + tv.T.String() + ")" + builder.WriteString("(" + nilStr + " ") + tv.T.WriteString(builder) + builder.WriteString(")") + return builder } // Value may be N_Readonly roPre, roPost := "", "" @@ -425,19 +664,17 @@ func (tv *TypedValue) ProtectedSprint(seen *seenValues, considerDeclaredType boo } // *ArrayType, *SliceType, *StructType, *MapType if ps, ok := tv.V.(protectedStringer); ok { - return roPre + ps.ProtectedString(seen) + roPost - } else if s, ok := tv.V.(fmt.Stringer); ok { - // *NativeType - return roPre + s.String() + roPost + builder.WriteString(roPre) + ps.ProtectedString(builder, seen) + builder.WriteString(roPost) + return builder } - if debug { - panic(fmt.Sprintf( - "unexpected type %s", - tv.T.String())) - } else { - panic("should not happen") - } + // *NativeType + builder.WriteString(roPre) + tv.V.WriteString(builder) + builder.WriteString(roPost) + return builder } } @@ -446,57 +683,66 @@ func (tv *TypedValue) ProtectedSprint(seen *seenValues, considerDeclaredType boo // For gno debugging/testing. func (tv TypedValue) String() string { - return tv.ProtectedString(newSeenValues()) + builder := strings.Builder{} + tv.WriteString(&builder) + return builder.String() +} + +func (tv TypedValue) WriteString(builder Builder) Builder { + return tv.ProtectedString(builder, newSeenValues()) } -func (tv TypedValue) ProtectedString(seen *seenValues) string { +func (tv TypedValue) ProtectedString(builder Builder, seen *seenValues) Builder { if tv.IsUndefined() { - return "(undefined)" + builder.WriteString("(undefined)") + return builder } - vs := "" + builder.WriteByte('(') if tv.V == nil { switch baseOf(tv.T) { case BoolType, UntypedBoolType: - vs = fmt.Sprintf("%t", tv.GetBool()) + builder.WriteString(fmt.Sprintf("%t", tv.GetBool())) case StringType, UntypedStringType: - vs = fmt.Sprintf("%s", tv.GetString()) + builder.WriteString(fmt.Sprintf("%s", tv.GetString())) case IntType: - vs = fmt.Sprintf("%d", tv.GetInt()) + builder.WriteString(fmt.Sprintf("%d", tv.GetInt())) case Int8Type: - vs = fmt.Sprintf("%d", tv.GetInt8()) + builder.WriteString(fmt.Sprintf("%d", tv.GetInt8())) case Int16Type: - vs = fmt.Sprintf("%d", tv.GetInt16()) + builder.WriteString(fmt.Sprintf("%d", tv.GetInt16())) case Int32Type, UntypedRuneType: - vs = fmt.Sprintf("%d", tv.GetInt32()) + builder.WriteString(fmt.Sprintf("%d", tv.GetInt32())) case Int64Type: - vs = fmt.Sprintf("%d", tv.GetInt64()) + builder.WriteString(fmt.Sprintf("%d", tv.GetInt64())) case UintType: - vs = fmt.Sprintf("%d", tv.GetUint()) + builder.WriteString(fmt.Sprintf("%d", tv.GetUint())) case Uint8Type: - vs = fmt.Sprintf("%d", tv.GetUint8()) + builder.WriteString(fmt.Sprintf("%d", tv.GetUint8())) case DataByteType: - vs = fmt.Sprintf("%d", tv.GetDataByte()) + builder.WriteString(fmt.Sprintf("%d", tv.GetDataByte())) case Uint16Type: - vs = fmt.Sprintf("%d", tv.GetUint16()) + builder.WriteString(fmt.Sprintf("%d", tv.GetUint16())) case Uint32Type: - vs = fmt.Sprintf("%d", tv.GetUint32()) + builder.WriteString(fmt.Sprintf("%d", tv.GetUint32())) case Uint64Type: - vs = fmt.Sprintf("%d", tv.GetUint64()) + builder.WriteString(fmt.Sprintf("%d", tv.GetUint64())) case Float32Type: - vs = fmt.Sprintf("%v", math.Float32frombits(tv.GetFloat32())) + builder.WriteString(fmt.Sprintf("%v", math.Float32frombits(tv.GetFloat32()))) case Float64Type: - vs = fmt.Sprintf("%v", math.Float64frombits(tv.GetFloat64())) + builder.WriteString(fmt.Sprintf("%v", math.Float64frombits(tv.GetFloat64()))) // Complex types that require recusion protection. default: - vs = nilStr + builder.WriteString(nilStr) } } else { - vs = tv.ProtectedSprint(seen, false) if base := baseOf(tv.T); base == StringType || base == UntypedStringType { - vs = strconv.Quote(vs) + builder.WriteString(tv.V.String()) + } else { + tv.ProtectedSprint(builder, seen, false) } } - - ts := tv.T.String() - return fmt.Sprintf("(%s %s)", vs, ts) // TODO improve + builder.WriteString(" ") + tv.T.WriteString(builder) + builder.WriteString(")") + return builder } diff --git a/gnovm/pkg/gnolang/values_test.go b/gnovm/pkg/gnolang/values_test.go index 5f114fa81d7..db526522dce 100644 --- a/gnovm/pkg/gnolang/values_test.go +++ b/gnovm/pkg/gnolang/values_test.go @@ -18,6 +18,10 @@ func (m *mockTypedValueStruct) VisitAssociated(vis Visitor) (stop bool) { func (m *mockTypedValueStruct) String() string { return fmt.Sprintf("MockTypedValueStruct(%d)", m.field) } +func (m *mockTypedValueStruct) WriteString(builder Builder) Builder { + builder.WriteString(m.String()) + return builder +} func (m *mockTypedValueStruct) DeepFill(store Store) Value { return m diff --git a/gnovm/pkg/test/filetest.go b/gnovm/pkg/test/filetest.go index c8eb2d6a415..4497e397ae2 100644 --- a/gnovm/pkg/test/filetest.go +++ b/gnovm/pkg/test/filetest.go @@ -265,7 +265,7 @@ func (opts *TestOptions) runTest(m *gno.Machine, pkgPath, fname string, content rr.TypeCheckError = tcError switch v := r.(type) { case *gno.TypedValue: - rr.Error = v.Sprint(m) + rr.Error = v.Sprint(gno.NewStringBuilderWithGasMeter(m.GasMeter), m).String() case *gno.PreprocessError: rr.Error = v.Unwrap().Error() case gno.UnhandledPanicError: diff --git a/gnovm/tests/stdlibs/fmt/print.go b/gnovm/tests/stdlibs/fmt/print.go index c206e188dac..e36670ca99a 100644 --- a/gnovm/tests/stdlibs/fmt/print.go +++ b/gnovm/tests/stdlibs/fmt/print.go @@ -9,14 +9,14 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnolang" ) -func X_typeString(v gnolang.TypedValue) string { +func X_typeString(m *gnolang.Machine, v gnolang.TypedValue) string { if v.IsUndefined() { return "" } - return v.T.String() + return v.T.WriteString(gnolang.NewStringBuilderWithGasMeter(m.GasMeter)).String() } -func X_valueOfInternal(v gnolang.TypedValue) ( +func X_valueOfInternal(m *gnolang.Machine, v gnolang.TypedValue) ( kind, declaredName string, bytes uint64, base gnolang.TypedValue, @@ -27,7 +27,7 @@ func X_valueOfInternal(v gnolang.TypedValue) ( return } if dt, ok := v.T.(*gnolang.DeclaredType); ok { - declaredName = dt.String() + declaredName = dt.WriteString(gnolang.NewStringBuilderWithGasMeter(m.GasMeter)).String() } baseT := gnolang.BaseOf(v.T) base = gnolang.TypedValue{ diff --git a/gnovm/tests/stdlibs/generated.go b/gnovm/tests/stdlibs/generated.go index c6ba02b90c2..7edf384a76d 100644 --- a/gnovm/tests/stdlibs/generated.go +++ b/gnovm/tests/stdlibs/generated.go @@ -43,12 +43,14 @@ var nativeFuncs = [...]NativeFunc{ []gno.FieldTypeExpr{ {NameExpr: *gno.Nx("r0"), Type: gno.X("string")}, }, - false, + true, func(m *gno.Machine) { b := m.LastBlock() p0 := *(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV) - r0 := testlibs_fmt.X_typeString(p0) + r0 := testlibs_fmt.X_typeString( + m, + p0) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -70,12 +72,14 @@ var nativeFuncs = [...]NativeFunc{ {NameExpr: *gno.Nx("r3"), Type: gno.X("any")}, {NameExpr: *gno.Nx("r4"), Type: gno.X("int")}, }, - false, + true, func(m *gno.Machine) { b := m.LastBlock() p0 := *(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV) - r0, r1, r2, r3, r4 := testlibs_fmt.X_valueOfInternal(p0) + r0, r1, r2, r3, r4 := testlibs_fmt.X_valueOfInternal( + m, + p0) m.PushValue(gno.Go2GnoValue( m.Alloc,