package gen import ( "fmt" "io" "strconv" "github.com/tinylib/msgp/msgp" ) type sizeState uint8 const ( // need to write "s = ..." assign sizeState = iota // need to write "s += ..." add // can just append "+ ..." expr ) func sizes(w io.Writer) *sizeGen { return &sizeGen{ p: printer{w: w}, state: assign, } } type sizeGen struct { passes p printer state sizeState } func (s *sizeGen) Method() Method { return Size } func (s *sizeGen) Apply(dirs []string) error { return nil } func builtinSize(typ string) string { return "msgp." + typ + "Size" } // this lets us chain together addition // operations where possible func (s *sizeGen) addConstant(sz string) { if !s.p.ok() { return } switch s.state { case assign: s.p.print("\ns = " + sz) s.state = expr return case add: s.p.print("\ns += " + sz) s.state = expr return case expr: s.p.print(" + " + sz) return } panic("unknown size state") } func (s *sizeGen) Execute(p Elem) error { if !s.p.ok() { return s.p.err } p = s.applyall(p) if p == nil { return nil } if !IsPrintable(p) { return nil } s.p.comment("Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message") s.p.printf("\nfunc (%s %s) Msgsize() (s int) {", p.Varname(), imutMethodReceiver(p)) s.state = assign next(s, p) s.p.nakedReturn() return s.p.err } func (s *sizeGen) gStruct(st *Struct) { if !s.p.ok() { return } nfields := uint32(len(st.Fields)) if st.AsTuple { data := msgp.AppendArrayHeader(nil, nfields) s.addConstant(strconv.Itoa(len(data))) for i := range st.Fields { if !s.p.ok() { return } next(s, st.Fields[i].FieldElem) } } else { data := msgp.AppendMapHeader(nil, nfields) s.addConstant(strconv.Itoa(len(data))) for i := range st.Fields { data = data[:0] data = msgp.AppendString(data, st.Fields[i].FieldTag) s.addConstant(strconv.Itoa(len(data))) next(s, st.Fields[i].FieldElem) } } } func (s *sizeGen) gPtr(p *Ptr) { s.state = add // inner must use add s.p.printf("\nif %s == nil {\ns += msgp.NilSize\n} else {", p.Varname()) next(s, p.Value) s.state = add // closing block; reset to add s.p.closeblock() } func (s *sizeGen) gSlice(sl *Slice) { if !s.p.ok() { return } s.addConstant(builtinSize(arrayHeader)) // if the slice's element is a fixed size // (e.g. float64, [32]int, etc.), then // print the length times the element size directly if str, ok := fixedsizeExpr(sl.Els); ok { s.addConstant(fmt.Sprintf("(%s * (%s))", lenExpr(sl), str)) return } // add inside the range block, and immediately after s.state = add s.p.rangeBlock(sl.Index, sl.Varname(), s, sl.Els) s.state = add } func (s *sizeGen) gArray(a *Array) { if !s.p.ok() { return } s.addConstant(builtinSize(arrayHeader)) // if the array's children are a fixed // size, we can compile an expression // that always represents the array's wire size if str, ok := fixedsizeExpr(a); ok { s.addConstant(str) return } s.state = add s.p.rangeBlock(a.Index, a.Varname(), s, a.Els) s.state = add } func (s *sizeGen) gMap(m *Map) { s.addConstant(builtinSize(mapHeader)) vn := m.Varname() s.p.printf("\nif %s != nil {", vn) s.p.printf("\nfor %s, %s := range %s {", m.Keyidx, m.Validx, vn) s.p.printf("\n_ = %s", m.Validx) // we may not use the value s.p.printf("\ns += msgp.StringPrefixSize + len(%s)", m.Keyidx) s.state = expr next(s, m.Value) s.p.closeblock() s.p.closeblock() s.state = add } func (s *sizeGen) gBase(b *BaseElem) { if !s.p.ok() { return } if b.Convert && b.ShimMode == Convert { s.state = add vname := randIdent() s.p.printf("\nvar %s %s", vname, b.BaseType()) // ensure we don't get "unused variable" warnings from outer slice iterations s.p.printf("\n_ = %s", b.Varname()) s.p.printf("\ns += %s", basesizeExpr(b.Value, vname, b.BaseName())) s.state = expr } else { vname := b.Varname() if b.Convert { vname = tobaseConvert(b) } s.addConstant(basesizeExpr(b.Value, vname, b.BaseName())) } } // returns "len(slice)" func lenExpr(sl *Slice) string { return "len(" + sl.Varname() + ")" } // is a given primitive always the same (max) // size on the wire? func fixedSize(p Primitive) bool { switch p { case Intf, Ext, IDENT, Bytes, String: return false default: return true } } // strip reference from string func stripRef(s string) string { if s[0] == '&' { return s[1:] } return s } // return a fixed-size expression, if possible. // only possible for *BaseElem and *Array. // returns (expr, ok) func fixedsizeExpr(e Elem) (string, bool) { switch e := e.(type) { case *Array: if str, ok := fixedsizeExpr(e.Els); ok { return fmt.Sprintf("(%s * (%s))", e.Size, str), true } case *BaseElem: if fixedSize(e.Value) { return builtinSize(e.BaseName()), true } case *Struct: var str string for _, f := range e.Fields { if fs, ok := fixedsizeExpr(f.FieldElem); ok { if str == "" { str = fs } else { str += "+" + fs } } else { return "", false } } var hdrlen int mhdr := msgp.AppendMapHeader(nil, uint32(len(e.Fields))) hdrlen += len(mhdr) var strbody []byte for _, f := range e.Fields { strbody = msgp.AppendString(strbody[:0], f.FieldTag) hdrlen += len(strbody) } return fmt.Sprintf("%d + %s", hdrlen, str), true } return "", false } // print size expression of a variable name func basesizeExpr(value Primitive, vname, basename string) string { switch value { case Ext: return "msgp.ExtensionPrefixSize + " + stripRef(vname) + ".Len()" case Intf: return "msgp.GuessSize(" + vname + ")" case IDENT: return vname + ".Msgsize()" case Bytes: return "msgp.BytesPrefixSize + len(" + vname + ")" case String: return "msgp.StringPrefixSize + len(" + vname + ")" default: return builtinSize(basename) } }