1209 lines
31 KiB
Go
1209 lines
31 KiB
Go
/*
|
|
capnpc-go is the Cap'n proto code generator for Go. It reads a
|
|
CodeGeneratorRequest from stdin and for a file foo.capnp it writes
|
|
foo.capnp.go. This is usually invoked from `capnp compile -ogo`.
|
|
|
|
See https://capnproto.org/otherlang.html#how-to-write-compiler-plugins
|
|
for more details.
|
|
*/
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/zlib"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"go/format"
|
|
"math"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"zombiezen.com/go/capnproto2"
|
|
"zombiezen.com/go/capnproto2/internal/schema"
|
|
)
|
|
|
|
// Non-stdlib import paths.
|
|
const (
|
|
capnpImport = "zombiezen.com/go/capnproto2"
|
|
textImport = capnpImport + "/encoding/text"
|
|
schemasImport = capnpImport + "/schemas"
|
|
serverImport = capnpImport + "/server"
|
|
contextImport = "golang.org/x/net/context"
|
|
)
|
|
|
|
// genoptions are parameters that control code generation.
|
|
// Usually passed on the command line.
|
|
type genoptions struct {
|
|
promises bool
|
|
schemas bool
|
|
structStrings bool
|
|
}
|
|
|
|
type renderer interface {
|
|
Render(name string, params interface{}) error
|
|
Bytes() []byte
|
|
}
|
|
|
|
type templateRenderer struct {
|
|
buf bytes.Buffer
|
|
t *template.Template
|
|
}
|
|
|
|
// Render calls ExecuteTemplate to render to its buffer.
|
|
func (tr *templateRenderer) Render(name string, params interface{}) error {
|
|
return tr.t.ExecuteTemplate(&tr.buf, name, params)
|
|
}
|
|
|
|
// Bytes returns the accumulated bytes.
|
|
func (tr *templateRenderer) Bytes() []byte {
|
|
return tr.buf.Bytes()
|
|
}
|
|
|
|
// generator builds up the generated code for a single file.
|
|
type generator struct {
|
|
r renderer
|
|
fileID uint64
|
|
nodes nodeMap
|
|
imports imports
|
|
data staticData
|
|
opts genoptions
|
|
}
|
|
|
|
func newGenerator(fileID uint64, nodes nodeMap, opts genoptions) *generator {
|
|
g := &generator{
|
|
r: &templateRenderer{t: templates},
|
|
fileID: fileID,
|
|
nodes: nodes,
|
|
opts: opts,
|
|
}
|
|
g.imports.init()
|
|
g.data.init(fileID)
|
|
return g
|
|
}
|
|
|
|
// Basename returns the name of the schema file with the directory name removed.
|
|
func (g *generator) Basename() (string, error) {
|
|
f, err := g.nodes.mustFind(g.fileID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
dn, err := f.DisplayName()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return filepath.Base(dn), nil
|
|
}
|
|
|
|
func (g *generator) Imports() *imports {
|
|
return &g.imports
|
|
}
|
|
|
|
func (g *generator) Capnp() string {
|
|
return g.imports.Capnp()
|
|
}
|
|
|
|
// generate produces unformatted Go source code from the nodes defined in it.
|
|
func (g *generator) generate() []byte {
|
|
var out bytes.Buffer
|
|
out.WriteString("// Code generated by capnpc-go. DO NOT EDIT.\n\n")
|
|
fmt.Fprintf(&out, "package %s\n\n", g.nodes[g.fileID].pkg)
|
|
out.WriteString("import (\n")
|
|
for _, imp := range g.imports.usedImports() {
|
|
fmt.Fprintf(&out, "%v\n", imp)
|
|
}
|
|
out.WriteString(")\n")
|
|
out.Write(g.r.Bytes())
|
|
if len(g.data.buf) > 0 {
|
|
writeByteLiteral(&out, g.data.name, g.data.buf)
|
|
}
|
|
return out.Bytes()
|
|
}
|
|
|
|
func writeByteLiteral(out *bytes.Buffer, name string, data []byte) {
|
|
fmt.Fprintf(out, "var %s = []byte{", name)
|
|
for i, b := range data {
|
|
if i%8 == 0 {
|
|
out.WriteByte('\n')
|
|
}
|
|
fmt.Fprintf(out, "%d,", b)
|
|
}
|
|
fmt.Fprintf(out, "\n}\n")
|
|
}
|
|
|
|
func (g *generator) defineSchemaVar() error {
|
|
msg, seg, _ := capnp.NewMessage(capnp.SingleSegment(nil))
|
|
req, _ := schema.NewRootCodeGeneratorRequest(seg)
|
|
fnodes := g.nodes[g.fileID].nodes
|
|
ids := make([]uint64, len(fnodes))
|
|
for i, n := range fnodes {
|
|
ids[i] = n.Id()
|
|
}
|
|
sort.Sort(uint64Slice(ids))
|
|
// TODO(light): find largest object size and use that to allocate list
|
|
nodes, _ := req.NewNodes(int32(len(g.nodes)))
|
|
i := 0
|
|
for _, id := range ids {
|
|
n := g.nodes[id]
|
|
if err := nodes.Set(i, n.Node); err != nil {
|
|
return err
|
|
}
|
|
i++
|
|
}
|
|
var buf bytes.Buffer
|
|
z, _ := zlib.NewWriterLevel(&buf, zlib.BestCompression)
|
|
if err := capnp.NewPackedEncoder(z).Encode(msg); err != nil {
|
|
return err
|
|
}
|
|
if err := z.Close(); err != nil {
|
|
return err
|
|
}
|
|
return renderSchemaVar(g.r, schemaVarParams{
|
|
G: g,
|
|
FileID: g.fileID,
|
|
NodeIDs: ids,
|
|
schema: buf.Bytes(),
|
|
})
|
|
}
|
|
|
|
// importForNode returns the import spec needed to reference n from
|
|
// rel's scope. If none is needed (they are in the same package), then
|
|
// the zero importSpec is returned.
|
|
func importForNode(n, rel *node) (importSpec, error) {
|
|
if n.pkg == "" {
|
|
return importSpec{}, fmt.Errorf("internal error (bad schema?): missing package declaration for %s", n)
|
|
}
|
|
if n.imp == "" {
|
|
return importSpec{}, fmt.Errorf("internal error (bad schema?): missing import declaration for %s", n)
|
|
}
|
|
if rel.imp == "" {
|
|
return importSpec{}, fmt.Errorf("internal error (bad schema?): missing import declaration for %s", rel)
|
|
}
|
|
if n.imp == rel.imp {
|
|
return importSpec{}, nil
|
|
}
|
|
return importSpec{path: n.imp, name: n.pkg}, nil
|
|
}
|
|
|
|
func (g *generator) RemoteNodeNew(n, rel *node) (string, error) {
|
|
ref, err := makeNodeTypeRef(n, rel)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if ref.newfunc == "" {
|
|
return "", fmt.Errorf("no new function for %s", ref.name)
|
|
}
|
|
if ref.imp.path == "" {
|
|
return ref.newfunc, nil
|
|
}
|
|
qname := g.imports.add(ref.imp)
|
|
return qname + "." + ref.newfunc, nil
|
|
}
|
|
|
|
func (g *generator) RemoteNodeName(n, rel *node) (string, error) {
|
|
ref, err := makeNodeTypeRef(n, rel)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if ref.imp.path == "" {
|
|
return ref.name, nil
|
|
}
|
|
qname := g.imports.add(ref.imp)
|
|
return qname + "." + ref.name, nil
|
|
}
|
|
|
|
func (g *generator) RemoteTypeNew(t schema.Type, rel *node) (string, error) {
|
|
ref, err := makeTypeRef(t, rel, g.nodes)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if ref.newfunc == "" {
|
|
return "", fmt.Errorf("no new function for %s", ref.name)
|
|
}
|
|
if ref.imp.path == "" {
|
|
return ref.newfunc, nil
|
|
}
|
|
qname := g.imports.add(ref.imp)
|
|
return qname + "." + ref.newfunc, nil
|
|
}
|
|
|
|
func (g *generator) RemoteTypeName(t schema.Type, rel *node) (string, error) {
|
|
ref, err := makeTypeRef(t, rel, g.nodes)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if ref.imp.path == "" {
|
|
return ref.name, nil
|
|
}
|
|
qname := g.imports.add(ref.imp)
|
|
return qname + "." + ref.name, nil
|
|
}
|
|
|
|
func (g *generator) defineEnum(n *node) error {
|
|
es, _ := n.Enum().Enumerants()
|
|
ev := make([]enumval, es.Len())
|
|
for i := 0; i < es.Len(); i++ {
|
|
e := es.At(i)
|
|
ev[e.CodeOrder()] = makeEnumval(n, i, e)
|
|
}
|
|
nann, _ := n.Annotations()
|
|
err := renderEnum(g.r, enumParams{
|
|
G: g,
|
|
Node: n,
|
|
Annotations: parseAnnotations(nann),
|
|
EnumValues: ev,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("enum %s: %v", n, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func isValueOfType(v schema.Value, t schema.Type) bool {
|
|
// Ensure that the value is for the given type. The schema ensures the union ordinals match.
|
|
return !v.IsValid() || int(v.Which()) == int(t.Which())
|
|
}
|
|
|
|
// Value formats a value from a schema (like a field default) as Go source.
|
|
func (g *generator) Value(rel *node, t schema.Type, v schema.Value) (string, error) {
|
|
if !isValueOfType(v, t) {
|
|
return "", fmt.Errorf("value type is %v, but found %v value", t.Which(), v.Which())
|
|
}
|
|
|
|
switch t.Which() {
|
|
case schema.Type_Which_void:
|
|
return "struct{}{}", nil
|
|
|
|
case schema.Type_Which_interface:
|
|
// The only statically representable interface value is null.
|
|
return g.imports.Capnp() + ".Client(nil)", nil
|
|
|
|
case schema.Type_Which_bool:
|
|
if v.Bool() {
|
|
return "true", nil
|
|
} else {
|
|
return "false", nil
|
|
}
|
|
|
|
case schema.Type_Which_uint8, schema.Type_Which_uint16, schema.Type_Which_uint32, schema.Type_Which_uint64:
|
|
return fmt.Sprintf("uint%d(%d)", intbits(t.Which()), uintValue(v)), nil
|
|
|
|
case schema.Type_Which_int8, schema.Type_Which_int16, schema.Type_Which_int32, schema.Type_Which_int64:
|
|
return fmt.Sprintf("int%d(%d)", intbits(t.Which()), intValue(v)), nil
|
|
|
|
case schema.Type_Which_float32:
|
|
return fmt.Sprintf("%s.Float32frombits(0x%x)", g.imports.Math(), math.Float32bits(v.Float32())), nil
|
|
|
|
case schema.Type_Which_float64:
|
|
return fmt.Sprintf("%s.Float64frombits(0x%x)", g.imports.Math(), math.Float64bits(v.Float64())), nil
|
|
|
|
case schema.Type_Which_text:
|
|
text, _ := v.Text()
|
|
return strconv.Quote(text), nil
|
|
|
|
case schema.Type_Which_data:
|
|
buf := make([]byte, 0, 1024)
|
|
buf = append(buf, "[]byte{"...)
|
|
data, _ := v.Data()
|
|
for i, b := range data {
|
|
if i > 0 {
|
|
buf = append(buf, ',', ' ')
|
|
}
|
|
buf = strconv.AppendUint(buf, uint64(b), 10)
|
|
}
|
|
buf = append(buf, '}')
|
|
return string(buf), nil
|
|
|
|
case schema.Type_Which_enum:
|
|
en := g.nodes[t.Enum().TypeId()]
|
|
if en == nil || !en.IsValid() || en.Which() != schema.Node_Which_enum {
|
|
return "", errors.New("expected enum type")
|
|
}
|
|
enums, _ := en.Enum().Enumerants()
|
|
val := int(v.Enum())
|
|
if val >= enums.Len() {
|
|
rn, err := g.RemoteNodeName(en, rel)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("%s(%d)", rn, val), nil
|
|
}
|
|
ev := makeEnumval(en, val, enums.At(val))
|
|
imp, err := importForNode(en, rel)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if imp.path == "" {
|
|
return ev.FullName(), nil
|
|
}
|
|
qname := g.imports.add(imp)
|
|
return qname + "." + ev.FullName(), nil
|
|
|
|
case schema.Type_Which_structType:
|
|
data, _ := v.StructValuePtr()
|
|
var buf bytes.Buffer
|
|
tn, err := g.nodes.mustFind(t.StructType().TypeId())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
sd, err := g.data.copyData(data)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
err = templates.ExecuteTemplate(&buf, "structValue", structValueParams{
|
|
G: g,
|
|
Node: rel,
|
|
Typ: tn,
|
|
Value: sd,
|
|
})
|
|
return buf.String(), err
|
|
|
|
case schema.Type_Which_anyPointer:
|
|
data, _ := v.AnyPointerPtr()
|
|
var buf bytes.Buffer
|
|
sd, err := g.data.copyData(data)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
err = templates.ExecuteTemplate(&buf, "pointerValue", pointerValueParams{
|
|
G: g,
|
|
Value: sd,
|
|
})
|
|
return buf.String(), err
|
|
|
|
case schema.Type_Which_list:
|
|
data, _ := v.ListPtr()
|
|
var buf bytes.Buffer
|
|
ftyp, err := g.RemoteTypeName(t, rel)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
sd, err := g.data.copyData(data)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
err = templates.ExecuteTemplate(&buf, "listValue", listValueParams{
|
|
G: g,
|
|
Typ: ftyp,
|
|
Value: sd,
|
|
})
|
|
return buf.String(), err
|
|
default:
|
|
return "", fmt.Errorf("unhandled value type %v", t.Which())
|
|
}
|
|
}
|
|
|
|
func (g *generator) defineAnnotation(n *node) error {
|
|
err := renderAnnotation(g.r, annotationParams{
|
|
G: g,
|
|
Node: n,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("annotation %s: %v", n, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func isGoConstType(t schema.Type) bool {
|
|
w := t.Which()
|
|
return w == schema.Type_Which_bool ||
|
|
w == schema.Type_Which_int8 ||
|
|
w == schema.Type_Which_uint8 ||
|
|
w == schema.Type_Which_int16 ||
|
|
w == schema.Type_Which_uint16 ||
|
|
w == schema.Type_Which_int32 ||
|
|
w == schema.Type_Which_uint32 ||
|
|
w == schema.Type_Which_int64 ||
|
|
w == schema.Type_Which_uint64 ||
|
|
w == schema.Type_Which_text ||
|
|
w == schema.Type_Which_enum
|
|
}
|
|
|
|
func (g *generator) defineConstNodes(nodes []*node) error {
|
|
constNodes := make([]*node, 0, len(nodes))
|
|
for _, n := range nodes {
|
|
if n.Which() != schema.Node_Which_const {
|
|
continue
|
|
}
|
|
t, _ := n.Const().Type()
|
|
if isGoConstType(t) {
|
|
constNodes = append(constNodes, n)
|
|
}
|
|
}
|
|
nc := len(constNodes)
|
|
for _, n := range nodes {
|
|
if n.Which() != schema.Node_Which_const {
|
|
continue
|
|
}
|
|
t, _ := n.Const().Type()
|
|
if !isGoConstType(t) {
|
|
constNodes = append(constNodes, n)
|
|
}
|
|
}
|
|
if len(constNodes) == 0 {
|
|
// short path
|
|
return nil
|
|
}
|
|
err := renderConstants(g.r, constantsParams{
|
|
G: g,
|
|
Consts: constNodes[:nc],
|
|
Vars: constNodes[nc:],
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("file constants: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *generator) defineField(n *node, f field) (err error) {
|
|
defer func() {
|
|
if err != nil {
|
|
err = fmt.Errorf("field %s.%s: %v", n.shortDisplayName(), f.Name, err)
|
|
}
|
|
}()
|
|
|
|
fann, _ := f.Annotations()
|
|
ann := parseAnnotations(fann)
|
|
t, _ := f.Slot().Type()
|
|
def, _ := f.Slot().DefaultValue()
|
|
if !isValueOfType(def, t) {
|
|
return fmt.Errorf("default value type is %v, but found %v value", t.Which(), def.Which())
|
|
}
|
|
ftyp, err := g.RemoteTypeName(t, n)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
params := structFieldParams{
|
|
G: g,
|
|
Node: n,
|
|
Field: f,
|
|
Annotations: ann,
|
|
FieldType: ftyp,
|
|
}
|
|
switch t.Which() {
|
|
case schema.Type_Which_void:
|
|
return renderStructVoidField(g.r, structVoidFieldParams(params))
|
|
case schema.Type_Which_bool:
|
|
return renderStructBoolField(g.r, structBoolFieldParams{
|
|
structFieldParams: params,
|
|
Default: def.Bool(),
|
|
})
|
|
|
|
case schema.Type_Which_uint8, schema.Type_Which_uint16, schema.Type_Which_uint32, schema.Type_Which_uint64:
|
|
return renderStructUintField(g.r, structUintFieldParams{
|
|
structFieldParams: params,
|
|
Bits: intbits(t.Which()),
|
|
Default: uintValue(def),
|
|
})
|
|
|
|
case schema.Type_Which_int8, schema.Type_Which_int16, schema.Type_Which_int32, schema.Type_Which_int64:
|
|
return renderStructIntField(g.r, structIntFieldParams{
|
|
structUintFieldParams: structUintFieldParams{
|
|
structFieldParams: params,
|
|
Bits: intbits(t.Which()),
|
|
Default: uint64(intFieldDefaultMask(def)),
|
|
},
|
|
})
|
|
|
|
case schema.Type_Which_enum:
|
|
rn, err := g.RemoteTypeName(t, n)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return renderStructIntField(g.r, structIntFieldParams{
|
|
structUintFieldParams: structUintFieldParams{
|
|
structFieldParams: params,
|
|
Bits: 16,
|
|
Default: uint64(def.Enum()),
|
|
},
|
|
EnumName: rn,
|
|
})
|
|
case schema.Type_Which_float32:
|
|
return renderStructFloatField(g.r, structFloatFieldParams{
|
|
structFieldParams: params,
|
|
Bits: 32,
|
|
Default: uint64(math.Float32bits(def.Float32())),
|
|
})
|
|
|
|
case schema.Type_Which_float64:
|
|
return renderStructFloatField(g.r, structFloatFieldParams{
|
|
structFieldParams: params,
|
|
Bits: 64,
|
|
Default: math.Float64bits(def.Float64()),
|
|
})
|
|
|
|
case schema.Type_Which_text:
|
|
d, err := def.Text()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return renderStructTextField(g.r, structTextFieldParams{
|
|
structFieldParams: params,
|
|
Default: d,
|
|
})
|
|
|
|
case schema.Type_Which_data:
|
|
d, err := def.Data()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return renderStructDataField(g.r, structDataFieldParams{
|
|
structFieldParams: params,
|
|
Default: d,
|
|
})
|
|
|
|
case schema.Type_Which_structType:
|
|
var defref staticDataRef
|
|
if sf, err := def.StructValuePtr(); err != nil {
|
|
return err
|
|
} else if sf.IsValid() {
|
|
defref, err = g.data.copyData(sf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
tn, err := g.nodes.mustFind(t.StructType().TypeId())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return renderStructStructField(g.r, structStructFieldParams{
|
|
structFieldParams: params,
|
|
TypeNode: tn,
|
|
Default: defref,
|
|
})
|
|
|
|
case schema.Type_Which_anyPointer:
|
|
var defref staticDataRef
|
|
if p, err := def.AnyPointerPtr(); err != nil {
|
|
return err
|
|
} else if p.IsValid() {
|
|
defref, err = g.data.copyData(p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return renderStructPointerField(g.r, structPointerFieldParams{
|
|
structFieldParams: params,
|
|
Default: defref,
|
|
})
|
|
|
|
case schema.Type_Which_list:
|
|
var defref staticDataRef
|
|
if l, err := def.ListPtr(); err != nil {
|
|
return err
|
|
} else if l.IsValid() {
|
|
defref, err = g.data.copyData(l)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return renderStructListField(g.r, structListFieldParams{
|
|
structFieldParams: params,
|
|
Default: defref,
|
|
})
|
|
|
|
case schema.Type_Which_interface:
|
|
return renderStructInterfaceField(g.r, structInterfaceFieldParams(params))
|
|
default:
|
|
return fmt.Errorf("defining unhandled field type %v", t.Which())
|
|
}
|
|
}
|
|
|
|
// typeRef is a Go reference to a Cap'n Proto type.
|
|
type typeRef struct {
|
|
name string
|
|
newfunc string // if absent, there is no New function for this type.
|
|
imp importSpec // optional
|
|
}
|
|
|
|
func makeNodeTypeRef(n, rel *node) (typeRef, error) {
|
|
imp, err := importForNode(n, rel)
|
|
if err != nil {
|
|
return typeRef{}, err
|
|
}
|
|
switch n.Which() {
|
|
case schema.Node_Which_structNode:
|
|
return typeRef{
|
|
name: n.Name,
|
|
newfunc: "New" + n.Name,
|
|
imp: imp,
|
|
}, nil
|
|
case schema.Node_Which_enum, schema.Node_Which_interface:
|
|
return typeRef{
|
|
name: n.Name,
|
|
imp: imp,
|
|
}, nil
|
|
}
|
|
return typeRef{}, fmt.Errorf("unable to reference type of node %v", n.Which())
|
|
}
|
|
|
|
var (
|
|
staticTypeRefs = map[schema.Type_Which]typeRef{
|
|
schema.Type_Which_void: {},
|
|
schema.Type_Which_bool: {name: "bool"},
|
|
schema.Type_Which_int8: {name: "int8"},
|
|
schema.Type_Which_int16: {name: "int16"},
|
|
schema.Type_Which_int32: {name: "int32"},
|
|
schema.Type_Which_int64: {name: "int64"},
|
|
schema.Type_Which_uint8: {name: "uint8"},
|
|
schema.Type_Which_uint16: {name: "uint16"},
|
|
schema.Type_Which_uint32: {name: "uint32"},
|
|
schema.Type_Which_uint64: {name: "uint64"},
|
|
schema.Type_Which_float32: {name: "float32"},
|
|
schema.Type_Which_float64: {name: "float64"},
|
|
schema.Type_Which_text: {name: "string"},
|
|
schema.Type_Which_data: {name: "[]byte"},
|
|
schema.Type_Which_anyPointer: {name: "Pointer", imp: capnpImportSpec},
|
|
}
|
|
staticListTypeRefs = map[schema.Type_Which]typeRef{
|
|
// TODO(light): omitting newfunc since it doesn't have a similar type signature (no errors).
|
|
schema.Type_Which_void: typeRef{name: "VoidList", imp: capnpImportSpec},
|
|
|
|
schema.Type_Which_bool: typeRef{name: "BitList", newfunc: "NewBitList", imp: capnpImportSpec},
|
|
schema.Type_Which_int8: typeRef{name: "Int8List", newfunc: "NewInt8List", imp: capnpImportSpec},
|
|
schema.Type_Which_uint8: typeRef{name: "UInt8List", newfunc: "NewUInt8List", imp: capnpImportSpec},
|
|
schema.Type_Which_int16: typeRef{name: "Int16List", newfunc: "NewInt16List", imp: capnpImportSpec},
|
|
schema.Type_Which_uint16: typeRef{name: "UInt16List", newfunc: "NewUInt16List", imp: capnpImportSpec},
|
|
schema.Type_Which_int32: typeRef{name: "Int32List", newfunc: "NewInt32List", imp: capnpImportSpec},
|
|
schema.Type_Which_uint32: typeRef{name: "UInt32List", newfunc: "NewUInt32List", imp: capnpImportSpec},
|
|
schema.Type_Which_int64: typeRef{name: "Int64List", newfunc: "NewInt64List", imp: capnpImportSpec},
|
|
schema.Type_Which_uint64: typeRef{name: "UInt64List", newfunc: "NewUInt64List", imp: capnpImportSpec},
|
|
schema.Type_Which_float32: typeRef{name: "Float32List", newfunc: "NewFloat32List", imp: capnpImportSpec},
|
|
schema.Type_Which_float64: typeRef{name: "Float64List", newfunc: "NewFloat64List", imp: capnpImportSpec},
|
|
schema.Type_Which_text: typeRef{name: "TextList", newfunc: "NewTextList", imp: capnpImportSpec},
|
|
schema.Type_Which_data: typeRef{name: "DataList", newfunc: "NewDataList", imp: capnpImportSpec},
|
|
}
|
|
)
|
|
|
|
func makeTypeRef(t schema.Type, rel *node, nodes nodeMap) (typeRef, error) {
|
|
nodeRef := func(id uint64) (typeRef, error) {
|
|
ni, err := nodes.mustFind(id)
|
|
if err != nil {
|
|
return typeRef{}, err
|
|
}
|
|
return makeNodeTypeRef(ni, rel)
|
|
}
|
|
if ref, ok := staticTypeRefs[t.Which()]; ok {
|
|
return ref, nil
|
|
}
|
|
switch t.Which() {
|
|
case schema.Type_Which_enum:
|
|
return nodeRef(t.Enum().TypeId())
|
|
case schema.Type_Which_structType:
|
|
return nodeRef(t.StructType().TypeId())
|
|
case schema.Type_Which_interface:
|
|
return nodeRef(t.Interface().TypeId())
|
|
case schema.Type_Which_list:
|
|
lt, _ := t.List().ElementType()
|
|
if ref, ok := staticListTypeRefs[lt.Which()]; ok {
|
|
return ref, nil
|
|
}
|
|
switch lt.Which() {
|
|
case schema.Type_Which_enum:
|
|
ref, err := nodeRef(lt.Enum().TypeId())
|
|
if err != nil {
|
|
return ref, err
|
|
}
|
|
ref.name = ref.name + "_List"
|
|
ref.newfunc = "New" + ref.name
|
|
return ref, nil
|
|
case schema.Type_Which_structType:
|
|
ref, err := nodeRef(lt.StructType().TypeId())
|
|
if err != nil {
|
|
return ref, err
|
|
}
|
|
ref.name = ref.name + "_List"
|
|
ref.newfunc = "New" + ref.name
|
|
return ref, nil
|
|
case schema.Type_Which_anyPointer, schema.Type_Which_list, schema.Type_Which_interface:
|
|
return typeRef{name: "PointerList", newfunc: "NewPointerList", imp: capnpImportSpec}, nil
|
|
}
|
|
}
|
|
return typeRef{}, fmt.Errorf("unable to reference type %v", t.Which())
|
|
}
|
|
|
|
// intFieldDefaultMask returns the XOR mask used when getting or setting
|
|
// signed integer struct fields.
|
|
func intFieldDefaultMask(v schema.Value) uint64 {
|
|
mask := uint64(1)<<intbits(schema.Type_Which(v.Which())) - 1
|
|
return uint64(intValue(v)) & mask
|
|
}
|
|
|
|
// intValue returns the signed integer value of a schema value or zero
|
|
// if the value is invalid (the null pointer). Panics if the value is
|
|
// not a signed integer.
|
|
func intValue(v schema.Value) int64 {
|
|
if !v.IsValid() {
|
|
return 0
|
|
}
|
|
switch v.Which() {
|
|
case schema.Value_Which_int8:
|
|
return int64(v.Int8())
|
|
case schema.Value_Which_int16:
|
|
return int64(v.Int16())
|
|
case schema.Value_Which_int32:
|
|
return int64(v.Int32())
|
|
case schema.Value_Which_int64:
|
|
return v.Int64()
|
|
}
|
|
panic("unreachable")
|
|
}
|
|
|
|
// uintValue returns the unsigned integer value of a schema value or
|
|
// zero if the value is invalid (the null pointer). Panics if the value
|
|
// is not an unsigned integer.
|
|
func uintValue(v schema.Value) uint64 {
|
|
if !v.IsValid() {
|
|
return 0
|
|
}
|
|
switch v.Which() {
|
|
case schema.Value_Which_uint8:
|
|
return uint64(v.Uint8())
|
|
case schema.Value_Which_uint16:
|
|
return uint64(v.Uint16())
|
|
case schema.Value_Which_uint32:
|
|
return uint64(v.Uint32())
|
|
case schema.Value_Which_uint64:
|
|
return v.Uint64()
|
|
}
|
|
panic("unreachable")
|
|
}
|
|
|
|
// intbits returns the number of bits that an integer type requires.
|
|
func intbits(t schema.Type_Which) uint {
|
|
switch t {
|
|
case schema.Type_Which_uint8, schema.Type_Which_int8:
|
|
return 8
|
|
case schema.Type_Which_uint16, schema.Type_Which_int16:
|
|
return 16
|
|
case schema.Type_Which_uint32, schema.Type_Which_int32:
|
|
return 32
|
|
case schema.Type_Which_uint64, schema.Type_Which_int64:
|
|
return 64
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
}
|
|
|
|
func (g *generator) defineStruct(n *node) error {
|
|
if err := g.defineStructTypes(n, n); err != nil {
|
|
return err
|
|
}
|
|
if err := g.defineStructEnums(n); err != nil {
|
|
return err
|
|
}
|
|
if err := g.defineBaseStructFuncs(n); err != nil {
|
|
return err
|
|
}
|
|
if err := g.defineStructFuncs(n); err != nil {
|
|
return err
|
|
}
|
|
if err := g.defineStructList(n); err != nil {
|
|
return err
|
|
}
|
|
if g.opts.promises {
|
|
if err := g.defineStructPromise(n); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *generator) defineStructTypes(n, baseNode *node) error {
|
|
nann, _ := n.Annotations()
|
|
ann := parseAnnotations(nann)
|
|
err := renderStructTypes(g.r, structTypesParams{
|
|
G: g,
|
|
Node: n,
|
|
Annotations: ann,
|
|
BaseNode: baseNode,
|
|
})
|
|
if err != nil {
|
|
dn, _ := n.DisplayName()
|
|
return fmt.Errorf("struct type for %s: %v", dn, err)
|
|
}
|
|
|
|
for _, f := range n.codeOrderFields() {
|
|
if f.Which() == schema.Field_Which_group {
|
|
grp, err := g.nodes.mustFind(f.Group().TypeId())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := g.defineStructTypes(grp, baseNode); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *generator) defineStructEnums(n *node) error {
|
|
fields := n.codeOrderFields()
|
|
members := make([]field, 0, len(fields))
|
|
es := make(enumString, 0, len(fields))
|
|
for _, f := range fields {
|
|
if f.DiscriminantValue() != schema.Field_noDiscriminant {
|
|
members = append(members, f)
|
|
es = append(es, f.Name)
|
|
}
|
|
}
|
|
if n.StructNode().DiscriminantCount() > 0 {
|
|
err := renderStructEnums(g.r, structEnumsParams{
|
|
G: g,
|
|
Node: n,
|
|
Fields: members,
|
|
EnumString: es,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("struct enums for %s: %v", n, err)
|
|
}
|
|
}
|
|
for _, f := range fields {
|
|
if f.Which() == schema.Field_Which_group {
|
|
grp, err := g.nodes.mustFind(f.Group().TypeId())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := g.defineStructEnums(grp); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *generator) defineStructFuncs(n *node) error {
|
|
err := renderStructFuncs(g.r, structFuncsParams{
|
|
G: g,
|
|
Node: n,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("struct funcs for %s: %v", n, err)
|
|
}
|
|
|
|
for _, f := range n.codeOrderFields() {
|
|
switch f.Which() {
|
|
case schema.Field_Which_slot:
|
|
if err := g.defineField(n, f); err != nil {
|
|
return err
|
|
}
|
|
case schema.Field_Which_group:
|
|
grp, err := g.nodes.mustFind(f.Group().TypeId())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = renderStructGroup(g.r, structGroupParams{
|
|
G: g,
|
|
Node: n,
|
|
Group: grp,
|
|
Field: f,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("struct group for %s: %v", grp, err)
|
|
}
|
|
if err := g.defineStructFuncs(grp); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *generator) ObjectSize(n *node) (string, error) {
|
|
if n.Which() != schema.Node_Which_structNode {
|
|
return "", fmt.Errorf("object size called for %v node", n.Which())
|
|
}
|
|
return fmt.Sprintf("%s.ObjectSize{DataSize: %d, PointerCount: %d}",
|
|
g.imports.Capnp(),
|
|
int(n.StructNode().DataWordCount())*8,
|
|
n.StructNode().PointerCount()), nil
|
|
}
|
|
|
|
func (g *generator) defineBaseStructFuncs(n *node) error {
|
|
err := renderBaseStructFuncs(g.r, baseStructFuncsParams{
|
|
G: g,
|
|
Node: n,
|
|
StringMethod: g.opts.structStrings,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("base struct functions for %s: %v", n, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *generator) defineStructList(n *node) error {
|
|
err := renderStructList(g.r, structListParams{
|
|
G: g,
|
|
Node: n,
|
|
StringMethod: g.opts.structStrings,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("new struct function for %s: %v", n, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *generator) defineStructPromise(n *node) error {
|
|
err := renderPromise(g.r, promiseParams{
|
|
G: g,
|
|
Node: n,
|
|
Fields: n.codeOrderFields(),
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("promise for struct %s: %v", n, err)
|
|
}
|
|
|
|
for _, f := range n.codeOrderFields() {
|
|
switch f.Which() {
|
|
case schema.Field_Which_slot:
|
|
t, _ := f.Slot().Type()
|
|
if tw := t.Which(); tw != schema.Type_Which_structType && tw != schema.Type_Which_interface && tw != schema.Type_Which_anyPointer {
|
|
continue
|
|
}
|
|
if err := g.definePromiseField(n, f); err != nil {
|
|
return fmt.Errorf("promise field %s.%s: %v", n.shortDisplayName(), f.Name, err)
|
|
}
|
|
case schema.Field_Which_group:
|
|
grp, err := g.nodes.mustFind(f.Group().TypeId())
|
|
if err != nil {
|
|
return fmt.Errorf("promise group %s.%s: %v", n.shortDisplayName(), f.Name, err)
|
|
}
|
|
err = renderPromiseGroup(g.r, promiseGroupParams{
|
|
G: g,
|
|
Node: n,
|
|
Field: f,
|
|
Group: grp,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("promise for group %s: %v", grp, err)
|
|
}
|
|
if err := g.defineStructPromise(grp); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *generator) definePromiseField(n *node, f field) error {
|
|
slot := f.Slot()
|
|
switch t, _ := slot.Type(); t.Which() {
|
|
case schema.Type_Which_structType:
|
|
ni, err := g.nodes.mustFind(t.StructType().TypeId())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
params := promiseFieldStructParams{
|
|
G: g,
|
|
Node: n,
|
|
Field: f,
|
|
Struct: ni,
|
|
}
|
|
if def, _ := slot.DefaultValue(); def.IsValid() && def.Which() == schema.Value_Which_structValue {
|
|
if sf, _ := def.StructValuePtr(); sf.IsValid() {
|
|
params.Default, err = g.data.copyData(sf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return renderPromiseFieldStruct(g.r, params)
|
|
case schema.Type_Which_anyPointer:
|
|
return renderPromiseFieldAnyPointer(g.r, promiseFieldAnyPointerParams{
|
|
G: g,
|
|
Node: n,
|
|
Field: f,
|
|
})
|
|
case schema.Type_Which_interface:
|
|
ni, err := g.nodes.mustFind(t.Interface().TypeId())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return renderPromiseFieldInterface(g.r, promiseFieldInterfaceParams{
|
|
G: g,
|
|
Node: n,
|
|
Field: f,
|
|
Interface: ni,
|
|
})
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
}
|
|
|
|
func (g *generator) defineInterface(n *node) error {
|
|
m, err := methodSet(nil, n, g.nodes)
|
|
if err != nil {
|
|
return fmt.Errorf("building method set of interface %s: %v", n, err)
|
|
}
|
|
nann, _ := n.Annotations()
|
|
err = renderInterfaceClient(g.r, interfaceClientParams{
|
|
G: g,
|
|
Node: n,
|
|
Annotations: parseAnnotations(nann),
|
|
Methods: m,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("interface client %s: %v", n, err)
|
|
}
|
|
err = renderInterfaceServer(g.r, interfaceServerParams{
|
|
G: g,
|
|
Node: n,
|
|
Annotations: parseAnnotations(nann),
|
|
Methods: m,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("interface server %s: %v", n, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type enumString []string
|
|
|
|
func (es enumString) ValueString() string {
|
|
return strings.Join([]string(es), "")
|
|
}
|
|
|
|
func (es enumString) SliceFor(i int) string {
|
|
n := 0
|
|
for _, v := range es[:i] {
|
|
n += len(v)
|
|
}
|
|
return fmt.Sprintf("[%d:%d]", n, n+len(es[i]))
|
|
}
|
|
|
|
func (g *generator) defineFile() error {
|
|
f := g.nodes[g.fileID]
|
|
if f == nil {
|
|
return fmt.Errorf("no node in schema matches %#x", g.fileID)
|
|
}
|
|
if f.pkg == "" {
|
|
return errors.New("missing package annotation")
|
|
}
|
|
if f.imp == "" {
|
|
return errors.New("missing import annotation")
|
|
}
|
|
|
|
for _, n := range f.nodes {
|
|
if n.Which() == schema.Node_Which_annotation {
|
|
if err := g.defineAnnotation(n); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
if err := g.defineConstNodes(f.nodes); err != nil {
|
|
return err
|
|
}
|
|
for _, n := range f.nodes {
|
|
var err error
|
|
switch n.Which() {
|
|
case schema.Node_Which_enum:
|
|
err = g.defineEnum(n)
|
|
case schema.Node_Which_structNode:
|
|
if !n.StructNode().IsGroup() {
|
|
err = g.defineStruct(n)
|
|
}
|
|
case schema.Node_Which_interface:
|
|
err = g.defineInterface(n)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if g.opts.schemas {
|
|
if err := g.defineSchemaVar(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func generateFile(reqf schema.CodeGeneratorRequest_RequestedFile, nodes nodeMap, opts genoptions) error {
|
|
if opts.structStrings && !opts.schemas {
|
|
return errors.New("cannot generate struct String() methods without embedding schemas")
|
|
}
|
|
id := reqf.Id()
|
|
fname, _ := reqf.Filename()
|
|
g := newGenerator(id, nodes, opts)
|
|
if err := g.defineFile(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if dirPath, _ := filepath.Split(fname); dirPath != "" {
|
|
err := os.MkdirAll(dirPath, os.ModePerm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
unformatted := g.generate()
|
|
formatted, fmtErr := format.Source(unformatted)
|
|
if fmtErr != nil {
|
|
formatted = unformatted
|
|
}
|
|
|
|
file, err := os.Create(fname + ".go")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, werr := file.Write(formatted)
|
|
cerr := file.Close()
|
|
if fmtErr != nil {
|
|
return fmtErr
|
|
}
|
|
if werr != nil {
|
|
return err
|
|
}
|
|
if cerr != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
var opts genoptions
|
|
flag.BoolVar(&opts.promises, "promises", true, "generate code for promises")
|
|
flag.BoolVar(&opts.schemas, "schemas", true, "embed schema information in generated code")
|
|
flag.BoolVar(&opts.structStrings, "structstrings", true, "generate String() methods for structs (-schemas must be true)")
|
|
flag.Parse()
|
|
|
|
msg, err := capnp.NewDecoder(os.Stdin).Decode()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "capnpc-go: reading input:", err)
|
|
os.Exit(1)
|
|
}
|
|
req, err := schema.ReadRootCodeGeneratorRequest(msg)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "capnpc-go: reading input:", err)
|
|
os.Exit(1)
|
|
}
|
|
nodes, err := buildNodeMap(req)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "capnpc-go:", err)
|
|
os.Exit(1)
|
|
}
|
|
success := true
|
|
reqFiles, _ := req.RequestedFiles()
|
|
for i := 0; i < reqFiles.Len(); i++ {
|
|
reqf := reqFiles.At(i)
|
|
err := generateFile(reqf, nodes, opts)
|
|
if err != nil {
|
|
fname, _ := reqf.Filename()
|
|
fmt.Fprintf(os.Stderr, "capnpc-go: generating %s: %v\n", fname, err)
|
|
success = false
|
|
}
|
|
}
|
|
if !success {
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
type uint64Slice []uint64
|
|
|
|
func (p uint64Slice) Len() int { return len(p) }
|
|
func (p uint64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
func (p uint64Slice) Less(i, j int) bool { return p[i] < p[j] }
|