583 lines
15 KiB
Go
583 lines
15 KiB
Go
// Package text supports marshaling Cap'n Proto messages as text based on a schema.
|
|
package text
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"strconv"
|
|
|
|
"zombiezen.com/go/capnproto2"
|
|
"zombiezen.com/go/capnproto2/internal/nodemap"
|
|
"zombiezen.com/go/capnproto2/internal/schema"
|
|
"zombiezen.com/go/capnproto2/internal/strquote"
|
|
"zombiezen.com/go/capnproto2/schemas"
|
|
)
|
|
|
|
// Marker strings.
|
|
const (
|
|
voidMarker = "void"
|
|
interfaceMarker = "<external capability>"
|
|
anyPointerMarker = "<opaque pointer>"
|
|
)
|
|
|
|
// Marshal returns the text representation of a struct.
|
|
func Marshal(typeID uint64, s capnp.Struct) (string, error) {
|
|
buf := new(bytes.Buffer)
|
|
if err := NewEncoder(buf).Encode(typeID, s); err != nil {
|
|
return "", err
|
|
}
|
|
return buf.String(), nil
|
|
}
|
|
|
|
// MarshalList returns the text representation of a struct list.
|
|
func MarshalList(typeID uint64, l capnp.List) (string, error) {
|
|
buf := new(bytes.Buffer)
|
|
if err := NewEncoder(buf).EncodeList(typeID, l); err != nil {
|
|
return "", err
|
|
}
|
|
return buf.String(), nil
|
|
}
|
|
|
|
// An Encoder writes the text format of Cap'n Proto messages to an output stream.
|
|
type Encoder struct {
|
|
w indentWriter
|
|
tmp []byte
|
|
nodes nodemap.Map
|
|
}
|
|
|
|
// NewEncoder returns a new encoder that writes to w.
|
|
func NewEncoder(w io.Writer) *Encoder {
|
|
return &Encoder{w: indentWriter{w: w}}
|
|
}
|
|
|
|
// UseRegistry changes the registry that the encoder consults for
|
|
// schemas from the default registry.
|
|
func (enc *Encoder) UseRegistry(reg *schemas.Registry) {
|
|
enc.nodes.UseRegistry(reg)
|
|
}
|
|
|
|
// SetIndent sets string to indent each level with.
|
|
// An empty string disables indentation.
|
|
func (enc *Encoder) SetIndent(indent string) {
|
|
enc.w.indentPerLevel = indent
|
|
}
|
|
|
|
// Encode writes the text representation of s to the stream.
|
|
func (enc *Encoder) Encode(typeID uint64, s capnp.Struct) error {
|
|
if enc.w.err != nil {
|
|
return enc.w.err
|
|
}
|
|
err := enc.marshalStruct(typeID, s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return enc.w.err
|
|
}
|
|
|
|
// EncodeList writes the text representation of struct list l to the stream.
|
|
func (enc *Encoder) EncodeList(typeID uint64, l capnp.List) error {
|
|
_, seg, _ := capnp.NewMessage(capnp.SingleSegment(nil))
|
|
typ, _ := schema.NewRootType(seg)
|
|
typ.SetStructType()
|
|
typ.StructType().SetTypeId(typeID)
|
|
return enc.marshalList(typ, l)
|
|
}
|
|
|
|
func (enc *Encoder) marshalBool(v bool) {
|
|
if v {
|
|
enc.w.WriteString("true")
|
|
} else {
|
|
enc.w.WriteString("false")
|
|
}
|
|
}
|
|
|
|
func (enc *Encoder) marshalInt(i int64) {
|
|
enc.tmp = strconv.AppendInt(enc.tmp[:0], i, 10)
|
|
enc.w.Write(enc.tmp)
|
|
}
|
|
|
|
func (enc *Encoder) marshalUint(i uint64) {
|
|
enc.tmp = strconv.AppendUint(enc.tmp[:0], i, 10)
|
|
enc.w.Write(enc.tmp)
|
|
}
|
|
|
|
func (enc *Encoder) marshalFloat32(f float32) {
|
|
enc.tmp = strconv.AppendFloat(enc.tmp[:0], float64(f), 'g', -1, 32)
|
|
enc.w.Write(enc.tmp)
|
|
}
|
|
|
|
func (enc *Encoder) marshalFloat64(f float64) {
|
|
enc.tmp = strconv.AppendFloat(enc.tmp[:0], f, 'g', -1, 64)
|
|
enc.w.Write(enc.tmp)
|
|
}
|
|
|
|
func (enc *Encoder) marshalText(t []byte) {
|
|
enc.tmp = strquote.Append(enc.tmp[:0], t)
|
|
enc.w.Write(enc.tmp)
|
|
}
|
|
|
|
func needsEscape(b byte) bool {
|
|
return b < 0x20 || b >= 0x7f
|
|
}
|
|
|
|
func hexDigit(b byte) byte {
|
|
const digits = "0123456789abcdef"
|
|
return digits[b]
|
|
}
|
|
|
|
func (enc *Encoder) marshalStruct(typeID uint64, s capnp.Struct) error {
|
|
n, err := enc.nodes.Find(typeID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !n.IsValid() || n.Which() != schema.Node_Which_structNode {
|
|
return fmt.Errorf("cannot find struct type %#x", typeID)
|
|
}
|
|
var discriminant uint16
|
|
if n.StructNode().DiscriminantCount() > 0 {
|
|
discriminant = s.Uint16(capnp.DataOffset(n.StructNode().DiscriminantOffset() * 2))
|
|
}
|
|
fields := codeOrderFields(n.StructNode())
|
|
if len(fields) == 0 {
|
|
enc.w.WriteString("()")
|
|
return nil
|
|
}
|
|
enc.w.WriteByte('(')
|
|
enc.w.Indent()
|
|
enc.w.NewLine()
|
|
first := true
|
|
for _, f := range fields {
|
|
if !(f.Which() == schema.Field_Which_slot || f.Which() == schema.Field_Which_group) {
|
|
continue
|
|
}
|
|
if dv := f.DiscriminantValue(); !(dv == schema.Field_noDiscriminant || dv == discriminant) {
|
|
continue
|
|
}
|
|
if !first {
|
|
enc.w.WriteByte(',')
|
|
enc.w.NewLineOrSpace()
|
|
}
|
|
first = false
|
|
name, err := f.NameBytes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
enc.w.Write(name)
|
|
enc.w.WriteString(" = ")
|
|
switch f.Which() {
|
|
case schema.Field_Which_slot:
|
|
if err := enc.marshalFieldValue(s, f); err != nil {
|
|
return err
|
|
}
|
|
case schema.Field_Which_group:
|
|
if err := enc.marshalStruct(f.Group().TypeId(), s); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
enc.w.NewLine()
|
|
enc.w.Unindent()
|
|
enc.w.WriteByte(')')
|
|
return nil
|
|
}
|
|
|
|
func (enc *Encoder) marshalFieldValue(s capnp.Struct, f schema.Field) error {
|
|
typ, err := f.Slot().Type()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dv, err := f.Slot().DefaultValue()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if dv.IsValid() && int(typ.Which()) != int(dv.Which()) {
|
|
name, _ := f.Name()
|
|
return fmt.Errorf("marshal field %s: default value is a %v, want %v", name, dv.Which(), typ.Which())
|
|
}
|
|
switch typ.Which() {
|
|
case schema.Type_Which_void:
|
|
enc.w.WriteString(voidMarker)
|
|
case schema.Type_Which_bool:
|
|
v := s.Bit(capnp.BitOffset(f.Slot().Offset()))
|
|
d := dv.Bool()
|
|
enc.marshalBool(!d && v || d && !v)
|
|
case schema.Type_Which_int8:
|
|
v := s.Uint8(capnp.DataOffset(f.Slot().Offset()))
|
|
d := uint8(dv.Int8())
|
|
enc.marshalInt(int64(int8(v ^ d)))
|
|
case schema.Type_Which_int16:
|
|
v := s.Uint16(capnp.DataOffset(f.Slot().Offset() * 2))
|
|
d := uint16(dv.Int16())
|
|
enc.marshalInt(int64(int16(v ^ d)))
|
|
case schema.Type_Which_int32:
|
|
v := s.Uint32(capnp.DataOffset(f.Slot().Offset() * 4))
|
|
d := uint32(dv.Int32())
|
|
enc.marshalInt(int64(int32(v ^ d)))
|
|
case schema.Type_Which_int64:
|
|
v := s.Uint64(capnp.DataOffset(f.Slot().Offset() * 8))
|
|
d := uint64(dv.Int64())
|
|
enc.marshalInt(int64(v ^ d))
|
|
case schema.Type_Which_uint8:
|
|
v := s.Uint8(capnp.DataOffset(f.Slot().Offset()))
|
|
d := dv.Uint8()
|
|
enc.marshalUint(uint64(v ^ d))
|
|
case schema.Type_Which_uint16:
|
|
v := s.Uint16(capnp.DataOffset(f.Slot().Offset() * 2))
|
|
d := dv.Uint16()
|
|
enc.marshalUint(uint64(v ^ d))
|
|
case schema.Type_Which_uint32:
|
|
v := s.Uint32(capnp.DataOffset(f.Slot().Offset() * 4))
|
|
d := dv.Uint32()
|
|
enc.marshalUint(uint64(v ^ d))
|
|
case schema.Type_Which_uint64:
|
|
v := s.Uint64(capnp.DataOffset(f.Slot().Offset() * 8))
|
|
d := dv.Uint64()
|
|
enc.marshalUint(v ^ d)
|
|
case schema.Type_Which_float32:
|
|
v := s.Uint32(capnp.DataOffset(f.Slot().Offset() * 4))
|
|
d := math.Float32bits(dv.Float32())
|
|
enc.marshalFloat32(math.Float32frombits(v ^ d))
|
|
case schema.Type_Which_float64:
|
|
v := s.Uint64(capnp.DataOffset(f.Slot().Offset() * 8))
|
|
d := math.Float64bits(dv.Float64())
|
|
enc.marshalFloat64(math.Float64frombits(v ^ d))
|
|
case schema.Type_Which_structType:
|
|
p, err := s.Ptr(uint16(f.Slot().Offset()))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !p.IsValid() {
|
|
p, _ = dv.StructValuePtr()
|
|
}
|
|
return enc.marshalStruct(typ.StructType().TypeId(), p.Struct())
|
|
case schema.Type_Which_data:
|
|
p, err := s.Ptr(uint16(f.Slot().Offset()))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !p.IsValid() {
|
|
b, _ := dv.Data()
|
|
enc.marshalText(b)
|
|
return nil
|
|
}
|
|
enc.marshalText(p.Data())
|
|
case schema.Type_Which_text:
|
|
p, err := s.Ptr(uint16(f.Slot().Offset()))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !p.IsValid() {
|
|
b, _ := dv.TextBytes()
|
|
enc.marshalText(b)
|
|
return nil
|
|
}
|
|
enc.marshalText(p.TextBytes())
|
|
case schema.Type_Which_list:
|
|
elem, err := typ.List().ElementType()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p, err := s.Ptr(uint16(f.Slot().Offset()))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !p.IsValid() {
|
|
p, _ = dv.ListPtr()
|
|
}
|
|
return enc.marshalList(elem, p.List())
|
|
case schema.Type_Which_enum:
|
|
v := s.Uint16(capnp.DataOffset(f.Slot().Offset() * 2))
|
|
d := dv.Uint16()
|
|
return enc.marshalEnum(typ.Enum().TypeId(), v^d)
|
|
case schema.Type_Which_interface:
|
|
enc.w.WriteString(interfaceMarker)
|
|
case schema.Type_Which_anyPointer:
|
|
enc.w.WriteString(anyPointerMarker)
|
|
default:
|
|
return fmt.Errorf("unknown field type %v", typ.Which())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func codeOrderFields(s schema.Node_structNode) []schema.Field {
|
|
list, _ := s.Fields()
|
|
n := list.Len()
|
|
fields := make([]schema.Field, n)
|
|
for i := 0; i < n; i++ {
|
|
f := list.At(i)
|
|
fields[f.CodeOrder()] = f
|
|
}
|
|
return fields
|
|
}
|
|
|
|
func (enc *Encoder) marshalList(elem schema.Type, l capnp.List) error {
|
|
writeListItems := func(writeItem func(i int) error) error {
|
|
if l.Len() == 0 {
|
|
_, err := enc.w.WriteString("[]")
|
|
return err
|
|
}
|
|
enc.w.WriteByte('[')
|
|
enc.w.Indent()
|
|
enc.w.NewLine()
|
|
for i := 0; i < l.Len(); i++ {
|
|
err := writeItem(i)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if i == l.Len()-1 {
|
|
enc.w.NewLine()
|
|
} else {
|
|
enc.w.WriteByte(',')
|
|
enc.w.NewLineOrSpace()
|
|
}
|
|
}
|
|
enc.w.Unindent()
|
|
enc.w.WriteByte(']')
|
|
return nil
|
|
}
|
|
writeListItemsN := func(writeItem func(i int) (int, error)) error {
|
|
return writeListItems(func(i int) error {
|
|
_, err := writeItem(i)
|
|
return err
|
|
})
|
|
}
|
|
switch elem.Which() {
|
|
case schema.Type_Which_void:
|
|
return writeListItemsN(func(_ int) (int, error) {
|
|
return enc.w.WriteString("void")
|
|
})
|
|
case schema.Type_Which_bool:
|
|
p := capnp.BitList{List: l}
|
|
return writeListItemsN(func(i int) (int, error) {
|
|
if p.At(i) {
|
|
return enc.w.WriteString("true")
|
|
} else {
|
|
return enc.w.WriteString("false")
|
|
}
|
|
})
|
|
case schema.Type_Which_int8:
|
|
p := capnp.Int8List{List: l}
|
|
return writeListItemsN(func(i int) (int, error) {
|
|
return enc.w.WriteString(strconv.FormatInt(int64(p.At(i)), 10))
|
|
})
|
|
case schema.Type_Which_int16:
|
|
p := capnp.Int16List{List: l}
|
|
return writeListItemsN(func(i int) (int, error) {
|
|
return enc.w.WriteString(strconv.FormatInt(int64(p.At(i)), 10))
|
|
})
|
|
case schema.Type_Which_int32:
|
|
p := capnp.Int32List{List: l}
|
|
return writeListItemsN(func(i int) (int, error) {
|
|
return enc.w.WriteString(strconv.FormatInt(int64(p.At(i)), 10))
|
|
})
|
|
case schema.Type_Which_int64:
|
|
p := capnp.Int64List{List: l}
|
|
return writeListItemsN(func(i int) (int, error) {
|
|
return enc.w.WriteString(strconv.FormatInt(p.At(i), 10))
|
|
})
|
|
case schema.Type_Which_uint8:
|
|
p := capnp.UInt8List{List: l}
|
|
return writeListItemsN(func(i int) (int, error) {
|
|
return enc.w.WriteString(strconv.FormatUint(uint64(p.At(i)), 10))
|
|
})
|
|
case schema.Type_Which_uint16:
|
|
p := capnp.UInt16List{List: l}
|
|
return writeListItemsN(func(i int) (int, error) {
|
|
return enc.w.WriteString(strconv.FormatUint(uint64(p.At(i)), 10))
|
|
})
|
|
case schema.Type_Which_uint32:
|
|
p := capnp.UInt32List{List: l}
|
|
return writeListItemsN(func(i int) (int, error) {
|
|
return enc.w.WriteString(strconv.FormatUint(uint64(p.At(i)), 10))
|
|
})
|
|
case schema.Type_Which_uint64:
|
|
p := capnp.UInt64List{List: l}
|
|
return writeListItemsN(func(i int) (int, error) {
|
|
return enc.w.WriteString(strconv.FormatUint(p.At(i), 10))
|
|
})
|
|
case schema.Type_Which_float32:
|
|
p := capnp.Float32List{List: l}
|
|
return writeListItemsN(func(i int) (int, error) {
|
|
return enc.w.WriteString(strconv.FormatFloat(float64(p.At(i)), 'g', -1, 32))
|
|
})
|
|
case schema.Type_Which_float64:
|
|
p := capnp.Float64List{List: l}
|
|
return writeListItemsN(func(i int) (int, error) {
|
|
return enc.w.WriteString(strconv.FormatFloat(p.At(i), 'g', -1, 64))
|
|
})
|
|
case schema.Type_Which_data:
|
|
p := capnp.DataList{List: l}
|
|
return writeListItemsN(func(i int) (int, error) {
|
|
s, err := p.At(i)
|
|
if err != nil {
|
|
return enc.w.WriteString("<error>")
|
|
}
|
|
buf := strquote.Append(nil, s)
|
|
return enc.w.Write(buf)
|
|
})
|
|
case schema.Type_Which_text:
|
|
p := capnp.TextList{List: l}
|
|
return writeListItemsN(func(i int) (int, error) {
|
|
s, err := p.BytesAt(i)
|
|
if err != nil {
|
|
return enc.w.WriteString("<error>")
|
|
}
|
|
buf := strquote.Append(nil, s)
|
|
return enc.w.Write(buf)
|
|
})
|
|
case schema.Type_Which_structType:
|
|
return writeListItems(func(i int) error {
|
|
return enc.marshalStruct(elem.StructType().TypeId(), l.Struct(i))
|
|
})
|
|
case schema.Type_Which_list:
|
|
ee, err := elem.List().ElementType()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return writeListItems(func(i int) error {
|
|
p, err := capnp.PointerList{List: l}.PtrAt(i)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return enc.marshalList(ee, p.List())
|
|
})
|
|
case schema.Type_Which_enum:
|
|
il := capnp.UInt16List{List: l}
|
|
typ := elem.Enum().TypeId()
|
|
// TODO(light): only search for node once
|
|
return writeListItems(func(i int) error {
|
|
return enc.marshalEnum(typ, il.At(i))
|
|
})
|
|
case schema.Type_Which_interface:
|
|
return writeListItemsN(func(_ int) (int, error) {
|
|
return enc.w.WriteString(interfaceMarker)
|
|
})
|
|
case schema.Type_Which_anyPointer:
|
|
return writeListItemsN(func(_ int) (int, error) {
|
|
return enc.w.WriteString(anyPointerMarker)
|
|
})
|
|
default:
|
|
return fmt.Errorf("unknown list type %v", elem.Which())
|
|
}
|
|
}
|
|
|
|
func (enc *Encoder) marshalEnum(typ uint64, val uint16) error {
|
|
n, err := enc.nodes.Find(typ)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if n.Which() != schema.Node_Which_enum {
|
|
return fmt.Errorf("marshaling enum of type @%#x: type is not an enum", typ)
|
|
}
|
|
enums, err := n.Enum().Enumerants()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if int(val) >= enums.Len() {
|
|
enc.marshalUint(uint64(val))
|
|
return nil
|
|
}
|
|
name, err := enums.At(int(val)).NameBytes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
enc.w.Write(name)
|
|
return nil
|
|
}
|
|
|
|
// indentWriter is helper for writing indented text
|
|
type indentWriter struct {
|
|
w io.Writer
|
|
err error
|
|
|
|
// indentPerLevel is a string to prepend to a line for every level of indentation.
|
|
indentPerLevel string
|
|
|
|
// current indent level
|
|
currentIndent int
|
|
|
|
// hasLineContent is true when we have written something on the current line.
|
|
hasLineContent bool
|
|
}
|
|
|
|
func (iw *indentWriter) beforeWrite() {
|
|
if iw.err != nil {
|
|
return
|
|
}
|
|
if len(iw.indentPerLevel) > 0 && !iw.hasLineContent {
|
|
iw.hasLineContent = true
|
|
for i := 0; i < iw.currentIndent; i++ {
|
|
_, err := iw.w.Write([]byte(iw.indentPerLevel))
|
|
if err != nil {
|
|
iw.err = err
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (iw *indentWriter) Write(p []byte) (int, error) {
|
|
iw.beforeWrite()
|
|
if iw.err != nil {
|
|
return 0, iw.err
|
|
}
|
|
var n int
|
|
n, iw.err = iw.w.Write(p)
|
|
return n, iw.err
|
|
}
|
|
|
|
func (iw *indentWriter) WriteString(s string) (int, error) {
|
|
iw.beforeWrite()
|
|
if iw.err != nil {
|
|
return 0, iw.err
|
|
}
|
|
var n int
|
|
n, iw.err = io.WriteString(iw.w, s)
|
|
return n, iw.err
|
|
}
|
|
|
|
func (iw *indentWriter) WriteByte(b byte) error {
|
|
iw.beforeWrite()
|
|
if iw.err != nil {
|
|
return iw.err
|
|
}
|
|
if bw, ok := iw.w.(io.ByteWriter); ok {
|
|
iw.err = bw.WriteByte(b)
|
|
} else {
|
|
_, iw.err = iw.w.Write([]byte{b})
|
|
}
|
|
return iw.err
|
|
}
|
|
|
|
func (iw *indentWriter) Indent() {
|
|
iw.currentIndent++
|
|
}
|
|
|
|
func (iw *indentWriter) Unindent() {
|
|
iw.currentIndent--
|
|
}
|
|
|
|
func (iw *indentWriter) NewLine() {
|
|
if len(iw.indentPerLevel) > 0 && iw.hasLineContent {
|
|
if iw.err != nil {
|
|
return
|
|
}
|
|
if bw, ok := iw.w.(io.ByteWriter); ok {
|
|
iw.err = bw.WriteByte('\n')
|
|
} else {
|
|
_, iw.err = iw.w.Write([]byte{'\n'})
|
|
}
|
|
iw.hasLineContent = false
|
|
}
|
|
}
|
|
|
|
func (iw *indentWriter) NewLineOrSpace() {
|
|
if len(iw.indentPerLevel) > 0 {
|
|
iw.NewLine()
|
|
} else {
|
|
iw.WriteByte(' ')
|
|
}
|
|
}
|