mirror of
https://github.com/zap-proto/zip.git
synced 2026-10-19 15:38:57 +00:00
876 lines
30 KiB
Go
876 lines
30 KiB
Go
package zip
|
|
|
|
// Layouts — the source that STATES a type's ZAP wire instead of deriving one at
|
|
// every call. Nothing here runs on the served path; this is a generator.
|
|
//
|
|
// [LayoutOf] names three readers of the one derivation: the plane encodes
|
|
// against it, a .zap schema states it as `Name type @Offset`, and a generator
|
|
// emits it as constants. This is that generator. What it writes is a [Wire]
|
|
// implementation: two methods per type, offsets as constants, no reflection on
|
|
// the served path.
|
|
//
|
|
// # Why generate rather than reflect
|
|
//
|
|
// The derivation is cached; the COPY is not. Every reflective call walks the
|
|
// fields again and sizes a builder by guess — work whose answer was fixed the
|
|
// moment the type was declared.
|
|
//
|
|
// It is also the only way to carry an id. bytes_fixed[N] has an offset and a
|
|
// width, so the layout knows its shape and a schema states it correctly, and the
|
|
// reflective encoder refuses it outright — deliberately, because an id is
|
|
// exactly the case that should force a type to declare its own wire. An ids.ID
|
|
// is [32]byte, so a reply carrying one crosses on a codec or not at all.
|
|
//
|
|
// The Go SDK writes its own with this emitter rather than a second one, through
|
|
// the [naming] seam: it restates the fleet's types under its own names, so the
|
|
// offsets come from the declared type and the identifiers come from it. See
|
|
// [App.SDK].
|
|
//
|
|
// # The wire does not move
|
|
//
|
|
// The emitted code writes the SAME BYTES the reflective encoder writes, field
|
|
// for field and in the same order, for every field the reflective encoder can
|
|
// write at all. That is not a promise about intent — it falls out of reading the
|
|
// offsets from [LayoutOf] rather than deriving them a second time, and
|
|
// TestTheLayoutKeepsTheWire holds it against the reflective encoder directly.
|
|
//
|
|
// The sequence it reproduces: every list is written FIRST, because SetList takes
|
|
// an offset that must already exist; then the object is started at the declared
|
|
// size; then each field is set in declaration order; then the list slots are
|
|
// filled. Deferred text and bytes are laid down by the builder in the order they
|
|
// were set, so declaration order is load-bearing and not cosmetic.
|
|
//
|
|
// # What the emitted package depends on
|
|
//
|
|
// The ZAP builder, and nothing else. [Wire] is restated in the emitted file, the
|
|
// way internal/zapenc restates it, so a leaf module — an id package, a shared
|
|
// types package — can state its wire without taking on this one's dependencies.
|
|
// The restatement is a compile-time assertion per type, which is what makes a
|
|
// deleted codec a build failure rather than a silent return to reflection.
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"go/format"
|
|
"reflect"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Layout is one emitted file: the Go source stating the wire of every type in one
|
|
// package, and the names of the types it states.
|
|
type Layout struct {
|
|
Path string // the package's import path
|
|
Package string // its package clause
|
|
Types []string // the types declared, alphabetically
|
|
Source []byte
|
|
|
|
// Stated is true when every type here ALREADY answers for its own bytes.
|
|
// A generator writing into one module reads types from several, and the
|
|
// ones it cannot write to are only a problem while they are still silent —
|
|
// so this is what tells "another module has to release" from "another
|
|
// module already did".
|
|
Stated bool
|
|
}
|
|
|
|
// Layouts renders a [Wire] implementation for each of roots and for every struct
|
|
// nested below them, grouped by the package that owns the type.
|
|
//
|
|
// A nested value is reached BY ITS METHOD, so the set is closed downward: a
|
|
// parent that states its wire needs every struct it holds to state one too, id
|
|
// or no id. The exception is a value with no slots — see [empty] — which crosses
|
|
// as bytes the parent writes inline.
|
|
//
|
|
// A type the derivation refuses — one holding a map, an interface, or a fixed
|
|
// array of anything but bytes — comes back as an error naming it, because there
|
|
// is no layout to state and a codec that guessed one would speak a wire nobody
|
|
// reads. Answering that is a change to the TYPE, which is not a generator's to
|
|
// make.
|
|
func Layouts(roots ...reflect.Type) ([]Layout, error) {
|
|
want := map[reflect.Type]bool{}
|
|
var refused []string
|
|
for _, r := range roots {
|
|
if err := reach(r, want); err != nil {
|
|
refused = append(refused, err.Error())
|
|
}
|
|
}
|
|
if len(refused) > 0 {
|
|
sort.Strings(refused)
|
|
return nil, fmt.Errorf("zip: %s", strings.Join(dedup(refused), "; "))
|
|
}
|
|
|
|
byPath := map[string][]reflect.Type{}
|
|
for t := range want {
|
|
byPath[t.PkgPath()] = append(byPath[t.PkgPath()], t)
|
|
}
|
|
out := make([]Layout, 0, len(byPath))
|
|
for path, ts := range byPath {
|
|
sort.Slice(ts, func(i, j int) bool { return ts[i].Name() < ts[j].Name() })
|
|
c, err := one(path, ts, want)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.Stated = true
|
|
for _, t := range ts {
|
|
if !reflect.PointerTo(t).Implements(wired) {
|
|
c.Stated = false
|
|
break
|
|
}
|
|
}
|
|
out = append(out, c)
|
|
}
|
|
sort.Slice(out, func(i, j int) bool { return out[i].Path < out[j].Path })
|
|
return out, nil
|
|
}
|
|
|
|
var wired = reflect.TypeOf((*Wire)(nil)).Elem()
|
|
|
|
// reach adds t and everything below it to want.
|
|
func reach(t reflect.Type, want map[reflect.Type]bool) error {
|
|
t = under(t)
|
|
if t == nil || t.Kind() != reflect.Struct || want[t] {
|
|
return nil
|
|
}
|
|
shape, err := LayoutOf(t)
|
|
if err != nil {
|
|
return fmt.Errorf("%s has no layout: %w", goname(t), err)
|
|
}
|
|
want[t] = true
|
|
for _, s := range shape.Slots {
|
|
f, ok := held(t, s.Name)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if n := nests(f.Type); n != nil && !empty(n) {
|
|
if err := reach(n, want); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// empty reports a value with no slots — every field unexported, which is what a
|
|
// time.Time or a netip.AddrPort is from here. It crosses as a complete and EMPTY
|
|
// ZAP object and carries nothing, so a parent writes those bytes inline rather
|
|
// than reaching for a method, and there is nothing to declare a wire for. The
|
|
// value is lost either way; that is the reflective encoder's answer too, and
|
|
// changing it would be a different wire.
|
|
func empty(t reflect.Type) bool {
|
|
sh, err := LayoutOf(t)
|
|
return err == nil && len(sh.Slots) == 0
|
|
}
|
|
|
|
// nests is the struct a field reaches through a pointer or a list, or nil. A
|
|
// slot is "bytes" for both a []byte and a nested value, so the Go type is what
|
|
// tells them apart.
|
|
func nests(t reflect.Type) reflect.Type {
|
|
t = under(t)
|
|
if t.Kind() == reflect.Slice {
|
|
t = under(t.Elem())
|
|
}
|
|
if t.Kind() == reflect.Struct {
|
|
return t
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func under(t reflect.Type) reflect.Type {
|
|
for t != nil && t.Kind() == reflect.Pointer {
|
|
t = t.Elem()
|
|
}
|
|
return t
|
|
}
|
|
|
|
// held is the DIRECT field of that name. FieldByName would also find one
|
|
// promoted through an embedded struct, and an embedded struct is a slot of its
|
|
// own here — its fields are nested, never flattened.
|
|
func held(t reflect.Type, name string) (reflect.StructField, bool) {
|
|
for i := range t.NumField() {
|
|
if f := t.Field(i); f.Name == name {
|
|
return f, true
|
|
}
|
|
}
|
|
return reflect.StructField{}, false
|
|
}
|
|
|
|
func goname(t reflect.Type) string {
|
|
if t.PkgPath() == "" {
|
|
return t.String()
|
|
}
|
|
return t.PkgPath() + "." + t.Name()
|
|
}
|
|
|
|
func dedup(s []string) []string {
|
|
out := s[:0]
|
|
var last string
|
|
for _, v := range s {
|
|
if v != last {
|
|
out = append(out, v)
|
|
}
|
|
last = v
|
|
}
|
|
return out
|
|
}
|
|
|
|
// ---- emission --------------------------------------------------------------
|
|
|
|
// pkg is the import set of one emitted file: an alias per path, so a name that
|
|
// two packages share is unambiguous in the source rather than in a convention.
|
|
// wide records whether any list of scalars was emitted, which is the one helper
|
|
// the file may need.
|
|
type pkg struct {
|
|
self string
|
|
alias map[string]string
|
|
taken map[string]bool
|
|
plain map[string]bool // imports whose alias is their own name
|
|
// as is how a file that did NOT declare these types spells them. The Go SDK
|
|
// restates the fleet's types under its own names and emits their codec
|
|
// beside them, so the offsets come from the declared type and every
|
|
// identifier comes from here. nil is the ordinary case — a generator
|
|
// writing into the package that declared the types, where every name is the
|
|
// name the type already has.
|
|
as naming
|
|
}
|
|
|
|
// naming is what one emitted file calls the types it writes: a declaration's
|
|
// name, a field's name inside it, and the type as Go source. Three questions
|
|
// rather than one, because a rename does not reach them all the same way — an
|
|
// embedded field IS its type's name, and a field of a renamed type is not.
|
|
type naming interface {
|
|
name(reflect.Type) string
|
|
field(reflect.Type, string) string
|
|
spell(reflect.Type) string
|
|
}
|
|
|
|
// name is what the emitted file calls t.
|
|
func (p *pkg) name(t reflect.Type) string {
|
|
if p.as != nil {
|
|
return p.as.name(t)
|
|
}
|
|
return t.Name()
|
|
}
|
|
|
|
// field is what the emitted file calls t's field. It differs from the declared
|
|
// name only for an embedded field, which is spelled as its type — so a file
|
|
// that renamed the type renamed the field with it.
|
|
func (p *pkg) field(t reflect.Type, name string) string {
|
|
if p.as != nil {
|
|
return p.as.field(t, name)
|
|
}
|
|
return name
|
|
}
|
|
|
|
func (p *pkg) of(path, name string) string {
|
|
if path == "" || path == p.self {
|
|
return ""
|
|
}
|
|
if a, ok := p.alias[path]; ok {
|
|
return a
|
|
}
|
|
a := name
|
|
for n := 2; p.taken[a]; n++ {
|
|
a = name + strconv.Itoa(n)
|
|
}
|
|
p.taken[a] = true
|
|
p.alias[path] = a
|
|
if a == name {
|
|
p.plain[path] = true
|
|
}
|
|
return a
|
|
}
|
|
|
|
// std takes a standard-library import the emitted code needs directly.
|
|
func (p *pkg) std(path string) { p.of(path, path[strings.LastIndex(path, "/")+1:]) }
|
|
|
|
// spell renders t as Go source inside this file, taking the import it needs.
|
|
func (p *pkg) spell(t reflect.Type) string {
|
|
if p.as != nil {
|
|
// A file that restated these types spells them itself, and takes no
|
|
// import to do it: the restatement is why it has none to take.
|
|
return p.as.spell(t)
|
|
}
|
|
if t.Name() != "" {
|
|
if a := p.of(t.PkgPath(), strings.SplitN(t.String(), ".", 2)[0]); a != "" {
|
|
return a + "." + t.Name()
|
|
}
|
|
return t.Name()
|
|
}
|
|
switch t.Kind() {
|
|
case reflect.Pointer:
|
|
return "*" + p.spell(t.Elem())
|
|
case reflect.Slice:
|
|
return "[]" + p.spell(t.Elem())
|
|
case reflect.Array:
|
|
return "[" + strconv.Itoa(t.Len()) + "]" + p.spell(t.Elem())
|
|
}
|
|
return t.String()
|
|
}
|
|
|
|
func one(path string, ts []reflect.Type, want map[reflect.Type]bool) (Layout, error) {
|
|
p := &pkg{
|
|
self: path,
|
|
alias: map[string]string{},
|
|
taken: map[string]bool{"zap": true, "fmt": true},
|
|
plain: map[string]bool{},
|
|
}
|
|
var body bytes.Buffer
|
|
names := make([]string, 0, len(ts))
|
|
for _, t := range ts {
|
|
if err := declare(&body, t, p); err != nil {
|
|
return Layout{}, err
|
|
}
|
|
names = append(names, t.Name())
|
|
}
|
|
|
|
clause := path[strings.LastIndex(path, "/")+1:]
|
|
if len(ts) > 0 {
|
|
clause = strings.SplitN(ts[0].String(), ".", 2)[0]
|
|
}
|
|
|
|
var out bytes.Buffer
|
|
fmt.Fprintf(&out, "// Code generated by zip; DO NOT EDIT.\n\npackage %s\n\nimport (\n\t\"fmt\"\n", clause)
|
|
paths := make([]string, 0, len(p.alias))
|
|
for q := range p.alias {
|
|
paths = append(paths, q)
|
|
}
|
|
sort.Strings(paths)
|
|
for _, q := range paths {
|
|
if p.plain[q] && !strings.Contains(q, ".") {
|
|
fmt.Fprintf(&out, "\t%q\n", q)
|
|
}
|
|
}
|
|
fmt.Fprint(&out, "\n\tzap \"github.com/zap-proto/go\"\n")
|
|
for _, q := range paths {
|
|
if !p.plain[q] || strings.Contains(q, ".") {
|
|
fmt.Fprintf(&out, "\t%s %q\n", p.alias[q], q)
|
|
}
|
|
}
|
|
fmt.Fprint(&out, ")\n\n")
|
|
out.Write(body.Bytes())
|
|
|
|
src, err := format.Source(out.Bytes())
|
|
if err != nil {
|
|
return Layout{}, fmt.Errorf("zip: emitted source for %s does not parse: %w\n%s", path, err, out.String())
|
|
}
|
|
return Layout{Path: path, Package: clause, Types: names, Source: src}, nil
|
|
}
|
|
|
|
// stated is the compile-time assertion that a type answers for its own bytes.
|
|
// It is [Wire] written out where it is consumed, so an emitted package depends
|
|
// on the ZAP builder and nothing else — and written INLINE, because a named
|
|
// interface would be a package-level identifier the emitter cannot know is free.
|
|
// github.com/luxfi/utxo imports a package called wire.
|
|
//
|
|
// Both halves are required together: a type carrying one would encode from these
|
|
// constants and decode by reflection, which is two answers to where its layout
|
|
// lives.
|
|
const stated = `var _ interface {
|
|
MarshalZAP() ([]byte, error)
|
|
UnmarshalZAP([]byte) error
|
|
} = (*%s)(nil)
|
|
|
|
`
|
|
|
|
// emptying is the ZAP message a value with no slots crosses as. A type whose
|
|
// fields are all unexported — a time.Time, a netip.AddrPort — states nothing, so
|
|
// what crosses is a complete and empty object. It is what the reflective encoder
|
|
// writes for one, byte for byte.
|
|
const emptying = `// empty is the ZAP message a value with no slots crosses as.
|
|
func empty() []byte {
|
|
b := zap.NewBuilder(zap.HeaderSize)
|
|
b.StartObject(0).FinishAsRoot()
|
|
return b.Finish()
|
|
}
|
|
|
|
`
|
|
|
|
func declare(w *bytes.Buffer, t reflect.Type, p *pkg) error {
|
|
shape, err := LayoutOf(t)
|
|
if err != nil {
|
|
return fmt.Errorf("zip: %s: %w", goname(t), err)
|
|
}
|
|
name := p.name(t)
|
|
lo := lower(name)
|
|
|
|
fmt.Fprintf(w, "// ---- %s %s\n\n", name, strings.Repeat("-", max(1, 66-len(name))))
|
|
fmt.Fprint(w, "const (\n")
|
|
for _, s := range shape.Slots {
|
|
fmt.Fprintf(w, "\t%s%sAt = %d\n", lo, p.field(t, s.Name), s.Offset)
|
|
}
|
|
fmt.Fprintf(w, "\t%sSize = %d\n)\n\n", lo, shape.Size)
|
|
fmt.Fprintf(w, stated, name)
|
|
|
|
fmt.Fprintf(w, "// MarshalZAP writes %s from constant offsets.\n", name)
|
|
fmt.Fprintf(w, "func (x *%s) MarshalZAP() ([]byte, error) {\n", name)
|
|
// A nil receiver is an absent body, not an empty object — the answer the
|
|
// reflective encoder gives, and a handler returning a nil *Out reaches here
|
|
// as a non-nil interface holding one.
|
|
fmt.Fprint(w, "\tif x == nil {\n\t\treturn nil, nil\n\t}\n")
|
|
fmt.Fprintf(w, "\tb := zap.NewBuilder(%sSize + 256)\n", lo)
|
|
for _, s := range shape.Slots {
|
|
f, ok := held(t, s.Name)
|
|
if !ok {
|
|
continue
|
|
}
|
|
switch kindOf(s, f.Type) {
|
|
case aList:
|
|
if err := writeList(w, p.field(t, s.Name), s, f, p); err != nil {
|
|
return err
|
|
}
|
|
case aOpt:
|
|
if err := writeOpt(w, p.field(t, s.Name), s, f, p); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
fmt.Fprintf(w, "\tob := b.StartObject(%sSize)\n", lo)
|
|
for _, s := range shape.Slots {
|
|
f, ok := held(t, s.Name)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if listed(kindOf(s, f.Type)) {
|
|
continue
|
|
}
|
|
if err := writeField(w, lo, p.field(t, s.Name), s, f, p); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, s := range shape.Slots {
|
|
f, ok := held(t, s.Name)
|
|
if !ok || !listed(kindOf(s, f.Type)) {
|
|
continue
|
|
}
|
|
fn := p.field(t, s.Name)
|
|
v := lower(fn)
|
|
fmt.Fprintf(w, "\tif %sN > 0 {\n\t\tob.SetList(%s%sAt, %sAt, %sN)\n\t}\n", v, lo, fn, v, v)
|
|
}
|
|
fmt.Fprint(w, "\tob.FinishAsRoot()\n\treturn b.Finish(), nil\n}\n\n")
|
|
|
|
fmt.Fprintf(w, "// UnmarshalZAP reads %s out of the buffer that arrived.\n", name)
|
|
fmt.Fprintf(w, "func (x *%s) UnmarshalZAP(data []byte) error {\n", name)
|
|
// An empty message leaves the value untouched: a void reply is an absence,
|
|
// not a zero somebody might mistake for an answer.
|
|
fmt.Fprint(w, "\tif x == nil || len(data) == 0 {\n\t\treturn nil\n\t}\n")
|
|
fmt.Fprintf(w, "\tm, err := zap.Parse(data)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%s: %%w\", err)\n\t}\n", name)
|
|
// The root is taken only if a field reads it. A message with no slot to
|
|
// read still PARSES — an unreadable one is an error, not an empty value —
|
|
// but Go refuses a variable nobody uses.
|
|
var reads bytes.Buffer
|
|
for _, s := range shape.Slots {
|
|
f, ok := held(t, s.Name)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if err := readField(&reads, lo, p.field(t, s.Name), s, f, p); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if reads.Len() > 0 {
|
|
fmt.Fprint(w, "\to := m.Root()\n")
|
|
w.Write(reads.Bytes())
|
|
} else {
|
|
fmt.Fprint(w, "\t_ = m\n")
|
|
}
|
|
fmt.Fprint(w, "\treturn nil\n}\n\n")
|
|
return nil
|
|
}
|
|
|
|
// The shapes a slot takes on this wire. Everything else is a scalar, which the
|
|
// setter table answers for by its .zap name.
|
|
type form int
|
|
|
|
const (
|
|
aScalar form = iota
|
|
aFixed
|
|
aNest
|
|
aList
|
|
// aOpt is a pointer to a scalar, text or bytes: a list of at most one
|
|
// element, so a pointer to zero is not mistaken for no pointer.
|
|
aOpt
|
|
)
|
|
|
|
// listed is whether a slot is written before the object and filled by SetList
|
|
// after it — every list, including the one-element list an optional is.
|
|
func listed(f form) bool { return f == aList || f == aOpt }
|
|
|
|
func kindOf(s Slot, ft reflect.Type) form {
|
|
ft = under(ft)
|
|
switch {
|
|
case ft.Kind() == reflect.Array:
|
|
return aFixed
|
|
case ft.Kind() == reflect.Struct:
|
|
return aNest
|
|
case !strings.HasPrefix(s.Type, "list<"):
|
|
return aScalar
|
|
case ft.Kind() == reflect.Slice && ft.Elem().Kind() != reflect.Uint8:
|
|
return aList // a []byte is one value, so a list<…> over one is an optional
|
|
case s.Ptr:
|
|
return aOpt
|
|
}
|
|
return aScalar
|
|
}
|
|
|
|
// setter is the builder call for one .zap type and getter its reader; goType is
|
|
// what the reader hands back, for the conversion a defined type needs. They are
|
|
// one table so the two directions cannot name different widths.
|
|
var setter = map[string][3]string{
|
|
"bool": {"SetBool", "Bool", "bool"},
|
|
"i8": {"SetInt8", "Int8", "int8"},
|
|
"i16": {"SetInt16", "Int16", "int16"},
|
|
"i32": {"SetInt32", "Int32", "int32"},
|
|
"i64": {"SetInt64", "Int64", "int64"},
|
|
"u8": {"SetUint8", "Uint8", "uint8"},
|
|
"u16": {"SetUint16", "Uint16", "uint16"},
|
|
"u32": {"SetUint32", "Uint32", "uint32"},
|
|
"u64": {"SetUint64", "Uint64", "uint64"},
|
|
"f32": {"SetFloat32", "Float32", "float32"},
|
|
"f64": {"SetFloat64", "Float64", "float64"},
|
|
"text": {"SetText", "Text", "string"},
|
|
"bytes": {"SetBytes", "Bytes", "[]byte"},
|
|
}
|
|
|
|
// writeField emits one field's write. fn is what the file being written calls
|
|
// the field, which is the declared name unless that file restated the type.
|
|
func writeField(w *bytes.Buffer, lo, fn string, s Slot, f reflect.StructField, p *pkg) error {
|
|
at := lo + fn + "At"
|
|
ref, tab, end := "x."+fn, "\t", ""
|
|
if s.Ptr {
|
|
// An absent pointer writes nothing: a null, not a zero, and the slot is
|
|
// already zeroed.
|
|
fmt.Fprintf(w, "\tif x.%s != nil {\n", fn)
|
|
ref, tab, end = "(*x."+fn+")", "\t\t", "\t}\n"
|
|
}
|
|
switch kindOf(s, f.Type) {
|
|
case aFixed:
|
|
fmt.Fprintf(w, "%sob.SetBytesFixed(%s, %s[:])\n", tab, at, ref)
|
|
case aNest:
|
|
if empty(under(f.Type)) {
|
|
fmt.Fprintf(w, "%s{\n%s\teb := zap.NewBuilder(zap.HeaderSize)\n%s\teb.StartObject(0).FinishAsRoot()\n%s\tob.SetBytes(%s, eb.Finish())\n%s}\n",
|
|
tab, tab, tab, tab, at, tab)
|
|
break
|
|
}
|
|
// x.F reaches the pointer method on its own: a field of a pointer
|
|
// receiver is addressable, and a pointer field already is one.
|
|
fmt.Fprintf(w, "%sinner%s, err := x.%s.MarshalZAP()\n%sif err != nil {\n%s\treturn nil, err\n%s}\n",
|
|
tab, fn, fn, tab, tab, tab)
|
|
fmt.Fprintf(w, "%sob.SetBytes(%s, inner%s)\n", tab, at, fn)
|
|
default:
|
|
c, ok := setter[s.Type]
|
|
if !ok {
|
|
return fmt.Errorf("zip: field %s: no wire form for %s", s.Name, s.Type)
|
|
}
|
|
fmt.Fprintf(w, "%sob.%s(%s, %s(%s))\n", tab, c[0], at, c[2], ref)
|
|
}
|
|
fmt.Fprint(w, end)
|
|
return nil
|
|
}
|
|
|
|
func readField(w *bytes.Buffer, lo, fn string, s Slot, f reflect.StructField, p *pkg) error {
|
|
at := lo + fn + "At"
|
|
ft := f.Type
|
|
if s.Ptr {
|
|
ft = under(ft)
|
|
}
|
|
switch kindOf(s, f.Type) {
|
|
case aFixed:
|
|
if !s.Ptr {
|
|
fmt.Fprintf(w, "\tcopy(x.%s[:], o.BytesFixed(%s, %d))\n", fn, at, s.N)
|
|
return nil
|
|
}
|
|
// A POINTER to a fixed array. Reading straight into x.F[:] dereferences
|
|
// a nil, and the value arriving is always a fresh one — so every read of
|
|
// a type with an optional id PANICKED, on the plane, at the far end.
|
|
//
|
|
// An inline slot has no null: the writer leaves an absent pointer's
|
|
// bytes zeroed (see [writeField]), so all-zero is what absence looks
|
|
// like coming back, and a pointer to an all-zero array reads back nil.
|
|
// This is the one pointer that still cannot say present-and-zero: a
|
|
// scalar crosses as a list of at most one element, and bytes_fixed has no
|
|
// list form inline. Giving it one moves the wire of every optional id.
|
|
name := p.spell(ft)
|
|
fmt.Fprintf(w, "\tif raw := o.BytesFixed(%s, %d); len(raw) > 0 {\n", at, s.N)
|
|
fmt.Fprintf(w, "\t\tvar v %s\n\t\tcopy(v[:], raw)\n", name)
|
|
fmt.Fprintf(w, "\t\tif v != (%s{}) {\n\t\t\tx.%s = &v\n\t\t}\n\t}\n", name, fn)
|
|
return nil
|
|
case aNest:
|
|
if empty(under(f.Type)) {
|
|
return nil // nothing crossed, so there is nothing to read back
|
|
}
|
|
fmt.Fprintf(w, "\tif raw := o.Bytes(%s); len(raw) > 0 {\n", at)
|
|
if s.Ptr {
|
|
fmt.Fprintf(w, "\t\tvar v %s\n\t\tif err := v.UnmarshalZAP(raw); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tx.%s = &v\n", p.spell(ft), fn)
|
|
} else {
|
|
fmt.Fprintf(w, "\t\tif err := x.%s.UnmarshalZAP(raw); err != nil {\n\t\t\treturn err\n\t\t}\n", fn)
|
|
}
|
|
fmt.Fprint(w, "\t}\n")
|
|
return nil
|
|
case aList:
|
|
return readList(w, at, fn, s, f, p)
|
|
case aOpt:
|
|
return readOpt(w, at, fn, s, f, p)
|
|
}
|
|
c, ok := setter[s.Type]
|
|
if !ok {
|
|
return fmt.Errorf("zip: field %s: no wire form for %s", s.Name, s.Type)
|
|
}
|
|
spell := p.spell(ft)
|
|
read := fmt.Sprintf("%s(o.%s(%s))", spell, c[1], at)
|
|
switch s.Type {
|
|
case "bytes":
|
|
read = fmt.Sprintf("%s(append([]byte(nil), o.Bytes(%s)...))", spell, at)
|
|
case "text":
|
|
// ZAP decodes a string zero-copy, over the frame that arrived, and the
|
|
// next call on that connection reuses the frame. A reply that outlives
|
|
// its call would mutate under its owner, so the codec copies here.
|
|
read = fmt.Sprintf("%s(strings.Clone(o.Text(%s)))", spell, at)
|
|
p.std("strings")
|
|
}
|
|
fmt.Fprintf(w, "\tx.%s = %s\n", fn, read)
|
|
return nil
|
|
}
|
|
|
|
// writeOpt emits an optional's one-element list, written before the object as
|
|
// every list is. Its element is what a list element of the same type is, so the
|
|
// reflective encoder and this agree byte for byte.
|
|
func writeOpt(w *bytes.Buffer, fn string, s Slot, f reflect.StructField, p *pkg) error {
|
|
v := lower(fn)
|
|
ref := "(*x." + fn + ")"
|
|
fmt.Fprintf(w, "\t%sAt, %sN := 0, 0\n\tif x.%s != nil {\n", v, v, fn)
|
|
switch s.Elem {
|
|
case "text", "bytes":
|
|
fmt.Fprintf(w, "\t\tenc := []byte(%s)\n", ref)
|
|
case "bool":
|
|
fmt.Fprintf(w, "\t\tenc := []byte{0}\n\t\tif %s {\n\t\t\tenc[0] = 1\n\t\t}\n", ref)
|
|
default:
|
|
if _, ok := setter[s.Elem]; !ok {
|
|
return fmt.Errorf("zip: field %s: no wire form for %s", s.Name, s.Type)
|
|
}
|
|
fmt.Fprint(w, "\t\tvar full [8]byte\n")
|
|
fmt.Fprintf(w, "\t\tbinary.LittleEndian.PutUint64(full[:], %s)\n", bits(s.Elem, ref))
|
|
fmt.Fprintf(w, "\t\tenc := full[:%d]\n", widthOf(s.Elem))
|
|
if s.Elem == "f32" || s.Elem == "f64" {
|
|
p.std("math")
|
|
}
|
|
}
|
|
fmt.Fprintf(w, "\t\t%sAt = b.WriteBytes(append(binary.LittleEndian.AppendUint32(nil, uint32(len(enc))), enc...))\n", v)
|
|
fmt.Fprintf(w, "\t\t%sN = 1\n\t}\n", v)
|
|
p.std("encoding/binary")
|
|
return nil
|
|
}
|
|
|
|
// readOpt emits the read of an optional: present exactly when its list holds an
|
|
// element, whatever that element's value.
|
|
func readOpt(w *bytes.Buffer, at, fn string, s Slot, f reflect.StructField, p *pkg) error {
|
|
spell := p.spell(under(f.Type))
|
|
fmt.Fprintf(w, "\tif l := o.List(%s); l.Len() > 0 {\n\t\traw := l.BytesAt(0)\n", at)
|
|
switch s.Elem {
|
|
case "text":
|
|
fmt.Fprintf(w, "\t\tv := %s(raw)\n", spell)
|
|
case "bytes":
|
|
fmt.Fprintf(w, "\t\tv := %s(append([]byte(nil), raw...))\n", spell)
|
|
case "bool":
|
|
fmt.Fprintf(w, "\t\tv := %s(len(raw) > 0 && raw[0] != 0)\n", spell)
|
|
default:
|
|
if _, ok := setter[s.Elem]; !ok {
|
|
return fmt.Errorf("zip: field %s: no wire form for %s", s.Name, s.Type)
|
|
}
|
|
fmt.Fprint(w, "\t\tvar full [8]byte\n\t\tcopy(full[:], raw)\n")
|
|
fmt.Fprintf(w, "\t\tv := %s(%s)\n", spell, elementRead(s))
|
|
p.std("encoding/binary")
|
|
if s.Elem == "f32" || s.Elem == "f64" {
|
|
p.std("math")
|
|
}
|
|
}
|
|
fmt.Fprintf(w, "\t\tx.%s = &v\n\t}\n", fn)
|
|
return nil
|
|
}
|
|
|
|
func writeList(w *bytes.Buffer, fn string, s Slot, f reflect.StructField, p *pkg) error {
|
|
v := lower(fn)
|
|
ref := "x." + fn
|
|
if s.Ptr {
|
|
ref = "(*x." + fn + ")"
|
|
}
|
|
et := under(f.Type).Elem()
|
|
ptr := et.Kind() == reflect.Pointer
|
|
el := under(et)
|
|
|
|
var elem bytes.Buffer
|
|
w, outer := &elem, w
|
|
switch {
|
|
case el.Kind() == reflect.Struct && empty(el):
|
|
fmt.Fprint(w, "\t\t\teb := zap.NewBuilder(zap.HeaderSize)\n\t\t\teb.StartObject(0).FinishAsRoot()\n\t\t\tenc := eb.Finish()\n")
|
|
case el.Kind() == reflect.Struct:
|
|
// A nil element encodes as the zero value, which is what the reflective
|
|
// encoder writes for one: a list has no hole to leave.
|
|
if ptr {
|
|
fmt.Fprintf(w, "\t\t\telem := %s[i]\n\t\t\tif elem == nil {\n\t\t\t\telem = new(%s)\n\t\t\t}\n", ref, p.spell(el))
|
|
fmt.Fprint(w, "\t\t\tenc, err := elem.MarshalZAP()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n")
|
|
} else {
|
|
fmt.Fprintf(w, "\t\t\tenc, err := %s[i].MarshalZAP()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n", ref)
|
|
}
|
|
case el.Kind() == reflect.Array:
|
|
fmt.Fprintf(w, "\t\t\tenc := %s[i][:]\n", ref)
|
|
case s.Elem == "text" || s.Elem == "bytes":
|
|
fmt.Fprintf(w, "\t\t\tenc := []byte(%s[i])\n", ref)
|
|
case s.Elem == "bool":
|
|
fmt.Fprintf(w, "\t\t\tenc := []byte{0}\n\t\t\tif %s[i] {\n\t\t\t\tenc[0] = 1\n\t\t\t}\n", ref)
|
|
default:
|
|
c, ok := setter[s.Elem]
|
|
if !ok {
|
|
return fmt.Errorf("zip: field %s: no wire form for list element %s", s.Name, s.Elem)
|
|
}
|
|
_ = c
|
|
fmt.Fprint(w, "\t\t\tvar full [8]byte\n")
|
|
fmt.Fprintf(w, "\t\t\tbinary.LittleEndian.PutUint64(full[:], %s)\n", element(s, ref))
|
|
fmt.Fprintf(w, "\t\t\tenc := full[:%d]\n", widthOf(s.Elem))
|
|
if s.Elem == "f32" || s.Elem == "f64" {
|
|
p.std("math")
|
|
}
|
|
}
|
|
fmt.Fprint(w, "\t\t\tvar n [4]byte\n\t\t\tbinary.LittleEndian.PutUint32(n[:], uint32(len(enc)))\n")
|
|
fmt.Fprint(w, "\t\t\tblob = append(blob, n[:]...)\n\t\t\tblob = append(blob, enc...)\n\t\t}\n")
|
|
|
|
w = outer
|
|
if s.Ptr {
|
|
// An absent pointer writes nothing, and its length cannot be asked for.
|
|
fmt.Fprintf(w, "\t%sAt, %sN := 0, 0\n\tif x.%s != nil {\n\t\t%sN = len(%s)\n\t}\n", v, v, fn, v, ref)
|
|
} else {
|
|
fmt.Fprintf(w, "\t%sAt, %sN := 0, len(%s)\n", v, v, ref)
|
|
}
|
|
fmt.Fprintf(w, "\tif %sN > 0 {\n\t\tvar blob []byte\n\t\tfor %s range %s {\n", v, index(&elem), ref)
|
|
w.Write(elem.Bytes())
|
|
fmt.Fprintf(w, "\t\t%sAt = b.WriteBytes(blob)\n\t}\n", v)
|
|
p.std("encoding/binary")
|
|
return nil
|
|
}
|
|
|
|
// index is the loop variable a list body needs, and nothing when it needs none:
|
|
// an element with no slots reads no index, and Go refuses a variable nobody uses.
|
|
func index(body *bytes.Buffer) string {
|
|
if bytes.Contains(body.Bytes(), []byte("[i]")) || bytes.Contains(body.Bytes(), []byte("(i)")) {
|
|
return "i :="
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// element spells one list element for the little-endian write.
|
|
func element(s Slot, ref string) string { return bits(s.Elem, ref+"[i]") }
|
|
|
|
// bits spells one scalar as the uint64 its little-endian bytes are cut from. A
|
|
// float crosses as its bits, which is what the reflective encoder writes, so the
|
|
// two agree.
|
|
func bits(zaptype, v string) string {
|
|
switch zaptype {
|
|
case "f32":
|
|
return fmt.Sprintf("uint64(math.Float32bits(float32(%s)))", v)
|
|
case "f64":
|
|
return fmt.Sprintf("math.Float64bits(float64(%s))", v)
|
|
}
|
|
return fmt.Sprintf("uint64(%s)", v)
|
|
}
|
|
|
|
// readList emits a single pass over the list. Element i by index re-walks the
|
|
// stream from its start, so reading a list that way is quadratic in its length;
|
|
// the rows grow from the elements actually there rather than from the count the
|
|
// message claims.
|
|
func readList(w *bytes.Buffer, at, fn string, s Slot, f reflect.StructField, p *pkg) error {
|
|
ft := under(f.Type)
|
|
et := ft.Elem()
|
|
ptr := et.Kind() == reflect.Pointer
|
|
el := under(et)
|
|
fallible := el.Kind() == reflect.Struct && !empty(el)
|
|
|
|
var elem bytes.Buffer
|
|
w, outer := &elem, w
|
|
// A scalar element arrives as its value and is pointed at after; a struct
|
|
// element is decoded in place, so a pointer one is allocated first.
|
|
value, point := "e", ""
|
|
if ptr && el.Kind() != reflect.Struct {
|
|
value, point = "v", "\t\t\te := &v\n"
|
|
}
|
|
switch {
|
|
case el.Kind() == reflect.Struct:
|
|
if ptr {
|
|
fmt.Fprintf(w, "\t\t\te := new(%s)\n", p.spell(el))
|
|
} else {
|
|
fmt.Fprintf(w, "\t\t\tvar e %s\n", p.spell(el))
|
|
}
|
|
if fallible {
|
|
fmt.Fprint(w, "\t\t\tif ferr = e.UnmarshalZAP(raw); ferr != nil {\n\t\t\t\treturn false\n\t\t\t}\n")
|
|
}
|
|
case el.Kind() == reflect.Array:
|
|
fmt.Fprintf(w, "\t\t\tvar %s %s\n\t\t\tcopy(%s[:], raw)\n", value, p.spell(el), value)
|
|
case s.Elem == "text":
|
|
fmt.Fprintf(w, "\t\t\t%s := %s(raw)\n", value, p.spell(el))
|
|
case s.Elem == "bytes":
|
|
fmt.Fprintf(w, "\t\t\t%s := %s(append([]byte(nil), raw...))\n", value, p.spell(el))
|
|
case s.Elem == "bool":
|
|
fmt.Fprintf(w, "\t\t\t%s := %s(len(raw) > 0 && raw[0] != 0)\n", value, p.spell(el))
|
|
default:
|
|
fmt.Fprint(w, "\t\t\tvar full [8]byte\n\t\t\tcopy(full[:], raw)\n")
|
|
fmt.Fprintf(w, "\t\t\t%s := %s(%s)\n", value, p.spell(el), elementRead(s))
|
|
p.std("encoding/binary")
|
|
if s.Elem == "f32" || s.Elem == "f64" {
|
|
p.std("math")
|
|
}
|
|
}
|
|
fmt.Fprint(w, point)
|
|
w = outer
|
|
fmt.Fprintf(w, "\tif l := o.List(%s); l.Len() > 0 {\n\t\tvar rows %s\n", at, p.spell(ft))
|
|
if fallible {
|
|
fmt.Fprint(w, "\t\tvar ferr error\n")
|
|
}
|
|
fmt.Fprint(w, "\t\tl.EachBytes(func(_ int, raw []byte) bool {\n")
|
|
w.Write(elem.Bytes())
|
|
fmt.Fprint(w, "\t\t\trows = append(rows, e)\n\t\t\treturn true\n\t\t})\n")
|
|
if fallible {
|
|
fmt.Fprint(w, "\t\tif ferr != nil {\n\t\t\treturn ferr\n\t\t}\n")
|
|
}
|
|
assign := "x." + fn + " = rows"
|
|
if s.Ptr {
|
|
assign = "x." + fn + " = &rows"
|
|
}
|
|
fmt.Fprintf(w, "\t\t%s\n\t}\n", assign)
|
|
return nil
|
|
}
|
|
|
|
// elementRead widens one list element that arrived at its own width. Anything
|
|
// narrower than eight bytes was zero-extended into full by the caller; a signed
|
|
// element is then sign-extended from its own width, which is what the reflective
|
|
// decoder does — a negative int32 read as a uint64 is a very large number.
|
|
func elementRead(s Slot) string {
|
|
n := widthOf(s.Elem)
|
|
switch s.Elem {
|
|
case "i8", "i16", "i32", "i64":
|
|
return fmt.Sprintf("int64(binary.LittleEndian.Uint64(full[:])<<%d) >> %d", 64-8*n, 64-8*n)
|
|
case "f32":
|
|
return "math.Float32frombits(uint32(binary.LittleEndian.Uint64(full[:])))"
|
|
case "f64":
|
|
return "math.Float64frombits(binary.LittleEndian.Uint64(full[:]))"
|
|
}
|
|
return "binary.LittleEndian.Uint64(full[:])"
|
|
}
|
|
|
|
func widthOf(zaptype string) int {
|
|
switch zaptype {
|
|
case "bool", "i8", "u8":
|
|
return 1
|
|
case "i16", "u16":
|
|
return 2
|
|
case "i32", "u32", "f32":
|
|
return 4
|
|
}
|
|
return 8
|
|
}
|
|
|
|
func lower(s string) string {
|
|
if s == "" {
|
|
return s
|
|
}
|
|
return strings.ToLower(s[:1]) + s[1:]
|
|
}
|