blob: 807d2b5d3659154237a3317a23bcd0e203448d26 [file] [log] [blame] [edit]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package codegen
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"text/template"
"go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen"
cpp "go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen_cpp"
)
type Generator struct {
tmpls *template.Template
}
type TypedArgument struct {
ArgumentName string
ArgumentValue string
ArgumentType cpp.Type
Pointer bool
Nullable bool
Access bool
MutableAccess bool
}
// formatParam funcs are helpers that transform a type and name into a string
// for rendering in a template.
type formatParam func(string, cpp.Type) string
// visitSliceMembers visits each member of nested slices passed in and calls
// |fn| on each of them in depth first order.
func visitSliceMembers(val reflect.Value, fn func(interface{})) {
switch val.Type().Kind() {
case reflect.Slice:
for j := 0; j < val.Len(); j++ {
visitSliceMembers(val.Index(j), fn)
}
case reflect.Interface:
visitSliceMembers(val.Elem(), fn)
default:
fn(val.Interface())
}
}
// renderParams renders a nested list of parameter definitions.
// The parameter definitions are either strings or cpp.Parameters.
// Parameter structs are rendered with the supplied format func.
// The strings and formatted Parameters are joined with commas and returned.
func renderParams(format formatParam, list interface{}) string {
var (
buf bytes.Buffer
first = true
)
visitSliceMembers(reflect.ValueOf(list), func(val interface{}) {
if val == nil {
panic(fmt.Sprintf("Unexpected nil in %#v", list))
}
if first {
first = false
} else {
buf.WriteString(", ")
}
switch val := val.(type) {
case string:
buf.WriteString(val)
case cpp.Parameter:
n, t := val.NameAndType()
buf.WriteString(format(n, t))
default:
panic(fmt.Sprintf("Invalid RenderParams arg %#v", val))
}
})
return buf.String()
}
func param(n string, t cpp.Type) string {
if t.Kind == cpp.TypeKinds.Array || t.Kind == cpp.TypeKinds.Struct {
if !t.Nullable {
if t.IsResource {
return fmt.Sprintf("%s&& %s", t.String(), n)
}
return fmt.Sprintf("const %s& %s", t.String(), n)
}
}
if t.Kind == cpp.TypeKinds.Handle || t.Kind == cpp.TypeKinds.Request || t.Kind == cpp.TypeKinds.Protocol {
return fmt.Sprintf("%s&& %s", t.String(), n)
}
return fmt.Sprintf("%s %s", t.String(), n)
}
func forwardParam(n string, t cpp.Type) string {
if t.Kind == cpp.TypeKinds.Array || t.Kind == cpp.TypeKinds.Struct {
if t.IsResource && !t.Nullable {
return fmt.Sprintf("std::move(%s)", n)
}
} else if t.Kind == cpp.TypeKinds.Handle || t.Kind == cpp.TypeKinds.Request || t.Kind == cpp.TypeKinds.Protocol {
return fmt.Sprintf("std::move(%s)", n)
}
return n
}
func closeHandles(argumentName string, argumentValue string, argumentType cpp.Type, pointer bool, nullable bool, access bool, mutableAccess bool) string {
if !argumentType.IsResource {
return ""
}
name := argumentName
value := argumentValue
if access {
name = fmt.Sprintf("%s()", name)
value = name
} else if mutableAccess {
name = fmt.Sprintf("mutable_%s()", name)
value = name
}
switch argumentType.Kind {
case cpp.TypeKinds.Handle, cpp.TypeKinds.Request, cpp.TypeKinds.Protocol:
if pointer {
if nullable {
return fmt.Sprintf("if (%s != nullptr) { %s->reset(); }", name, name)
}
return fmt.Sprintf("%s->reset();", name)
} else {
return fmt.Sprintf("%s.reset();", name)
}
case cpp.TypeKinds.Array:
element_name := argumentName + "_element"
element_type := argumentType.ElementType
var buf bytes.Buffer
buf.WriteString("{\n")
buf.WriteString(fmt.Sprintf("%s* %s = %s.data();\n", element_type, element_name, value))
buf.WriteString(fmt.Sprintf("for (size_t i = 0; i < %s.size(); ++i, ++%s) {\n", value, element_name))
buf.WriteString(closeHandles(element_name, fmt.Sprintf("(*%s)", element_name), *element_type, true, false, false, false))
buf.WriteString("\n}\n}\n")
return buf.String()
case cpp.TypeKinds.Vector:
element_name := argumentName + "_element"
element_type := argumentType.ElementType
var buf bytes.Buffer
buf.WriteString("{\n")
buf.WriteString(fmt.Sprintf("%s* %s = %s.mutable_data();\n", element_type, element_name, value))
buf.WriteString(fmt.Sprintf("for (uint64_t i = 0; i < %s.count(); ++i, ++%s) {\n", value, element_name))
buf.WriteString(closeHandles(element_name, fmt.Sprintf("(*%s)", element_name), *element_type, true, false, false, false))
buf.WriteString("\n}\n}\n")
return buf.String()
default:
if pointer {
if nullable {
return fmt.Sprintf("if (%s != nullptr) { %s->_CloseHandles(); }", name, name)
}
return fmt.Sprintf("%s->_CloseHandles();", name)
} else {
return fmt.Sprintf("%s._CloseHandles();", name)
}
}
}
// These are the helper functions we inject for use by the templates.
var utilityFuncs = template.FuncMap{
"SyncCallTotalStackSize": func(m cpp.Method) int {
totalSize := 0
if m.Request.ClientAllocation.IsStack {
totalSize += m.Request.ClientAllocation.Size
}
if m.Response.ClientAllocation.IsStack {
totalSize += m.Response.ClientAllocation.Size
}
return totalSize
},
"CloseHandles": func(member cpp.Member,
access bool,
mutableAccess bool) string {
n, t := member.NameAndType()
return closeHandles(n, n, t, t.WirePointer, t.WirePointer, access, mutableAccess)
},
"RenderParams": func(params ...interface{}) string {
return renderParams(param, params)
},
"RenderForwardParams": func(params ...interface{}) string {
return renderParams(forwardParam, params)
},
"RenderInitMessage": func(params ...interface{}) string {
s := renderParams(func(n string, t cpp.Type) string {
return n + "(" + forwardParam(n, t) + ")"
}, params)
if len(s) == 0 {
return ""
}
return ": " + s
},
// List is a helper to return a list of its arguments.
"List": func(items ...interface{}) []interface{} {
return items
},
}
func NewGenerator() *Generator {
tmpls := template.New("LLCPPTemplates").
Funcs(cpp.MergeFuncMaps(cpp.CommonTemplateFuncs, utilityFuncs))
templates := []string{
fileHeaderTmpl,
fileSourceTmpl,
fragmentBitsTmpl,
fragmentClientAsyncMethodsTmpl,
fragmentClientSyncMethodsTmpl,
fragmentConstTmpl,
fragmentEnumTmpl,
fragmentEventSenderTmpl,
fragmentMethodCompleterBaseTmpl,
fragmentMethodRequestTmpl,
fragmentMethodResponseContextTmpl,
fragmentMethodResponseTmpl,
fragmentMethodResultTmpl,
fragmentMethodUnownedResultTmpl,
fragmentProtocolCallerTmpl,
fragmentProtocolClientImplTmpl,
fragmentProtocolDetailsTmpl,
fragmentProtocolDispatcherTmpl,
fragmentProtocolEventHandlerTmpl,
fragmentProtocolInterfaceTmpl,
fragmentProtocolSyncClientTmpl,
fragmentProtocolTmpl,
fragmentServiceTmpl,
fragmentStructTmpl,
fragmentSyncEventHandlerTmpl,
fragmentSyncRequestCallerAllocateTmpl,
fragmentTableTmpl,
fragmentUnionTmpl,
testBaseTmpl,
}
for _, t := range templates {
template.Must(tmpls.Parse(t))
}
return &Generator{
tmpls: tmpls,
}
}
func generateFile(filename, clangFormatPath string, contentGenerator func(wr io.Writer) error) error {
if err := os.MkdirAll(filepath.Dir(filename), os.ModePerm); err != nil {
return err
}
file, err := fidlgen.NewLazyWriter(filename)
if err != nil {
return err
}
generatedPipe, err := cpp.NewClangFormatter(clangFormatPath).FormatPipe(file)
if err != nil {
return err
}
if err := contentGenerator(generatedPipe); err != nil {
return err
}
return generatedPipe.Close()
}
func (gen *Generator) generateHeader(wr io.Writer, tree cpp.Root) error {
return gen.tmpls.ExecuteTemplate(wr, "Header", tree)
}
func (gen *Generator) generateSource(wr io.Writer, tree cpp.Root) error {
return gen.tmpls.ExecuteTemplate(wr, "Source", tree)
}
func (gen *Generator) generateTestBase(wr io.Writer, tree cpp.Root) error {
return gen.tmpls.ExecuteTemplate(wr, "TestBase", tree)
}
// GenerateHeader generates the LLCPP bindings header, and writes it into
// the target filename.
func (gen *Generator) GenerateHeader(tree cpp.Root, filename, clangFormatPath string) error {
return generateFile(filename, clangFormatPath, func(wr io.Writer) error {
return gen.generateHeader(wr, tree)
})
}
// GenerateSource generates the LLCPP bindings source, and writes it into
// the target filename.
func (gen *Generator) GenerateSource(tree cpp.Root, filename, clangFormatPath string) error {
return generateFile(filename, clangFormatPath, func(wr io.Writer) error {
return gen.generateSource(wr, tree)
})
}
// GenerateTestBase generates the LLCPP bindings test base header, and
// writes it into the target filename.
func (gen *Generator) GenerateTestBase(tree cpp.Root, filename, clangFormatPath string) error {
return generateFile(filename, clangFormatPath, func(wr io.Writer) error {
return gen.generateTestBase(wr, tree)
})
}